Spaces:
Running
Running
quentinL52 commited on
Commit ·
f88b8e8
1
Parent(s): a2e2b2d
update
Browse files- src/config/agents.yaml +92 -5
- src/config/app_config.py +38 -14
- src/config/skill_domain_map.json +393 -0
- src/config/tasks.yaml +279 -26
- src/data/metiers.json +1273 -0
- src/parser_flow/CV_agent_flow.py +716 -106
- src/services/cv_service.py +61 -5
src/config/agents.yaml
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
|
|
|
|
|
| 1 |
cv_splitter:
|
| 2 |
role: >
|
| 3 |
Expert en Structure Documentaire
|
|
@@ -6,8 +8,7 @@ cv_splitter:
|
|
| 6 |
backstory: >
|
| 7 |
Tu es un algorithme de parsing de haute précision capable de structurer n'importe quel document non structuré en format JSON clair.
|
| 8 |
Ta priorité est la fidélité de l'extraction et la séparation propre des sections.
|
| 9 |
-
verbose:
|
| 10 |
-
|
| 11 |
|
| 12 |
skills_extractor:
|
| 13 |
role: >
|
|
@@ -75,9 +76,95 @@ language_extractor:
|
|
| 75 |
|
| 76 |
identity_extractor:
|
| 77 |
role: >
|
| 78 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 79 |
goal: >
|
| 80 |
-
|
|
|
|
| 81 |
backstory: >
|
| 82 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 83 |
verbose: false
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ===== AGENTS EXISTANTS (copie de cv_parser_api) =====
|
| 2 |
+
|
| 3 |
cv_splitter:
|
| 4 |
role: >
|
| 5 |
Expert en Structure Documentaire
|
|
|
|
| 8 |
backstory: >
|
| 9 |
Tu es un algorithme de parsing de haute précision capable de structurer n'importe quel document non structuré en format JSON clair.
|
| 10 |
Ta priorité est la fidélité de l'extraction et la séparation propre des sections.
|
| 11 |
+
verbose: true
|
|
|
|
| 12 |
|
| 13 |
skills_extractor:
|
| 14 |
role: >
|
|
|
|
| 76 |
|
| 77 |
identity_extractor:
|
| 78 |
role: >
|
| 79 |
+
Expert en Extraction d'Identité et Analyse Nominale
|
| 80 |
+
goal: >
|
| 81 |
+
Extraire l'identité complète du candidat (Nom, Prénom) de manière ultra-robuste.
|
| 82 |
+
backstory: >
|
| 83 |
+
Expert en identification de personnes, capable de repérer un nom même s'il est placé
|
| 84 |
+
de manière inhabituelle ou s'il n'est mentionné qu'au tout début du document.
|
| 85 |
+
Tu sais utiliser le nom du fichier comme un "indice" précieux pour confirmer
|
| 86 |
+
ou découvrir l'identité si le texte du CV est ambigu ou incomplet.
|
| 87 |
+
Ta priorité est d'extraire le NOM et le PRÉNOM séparément et ensemble (Nom Complet).
|
| 88 |
+
verbose: false
|
| 89 |
+
|
| 90 |
+
# ===== Analyse =====
|
| 91 |
+
|
| 92 |
+
header_analyzer:
|
| 93 |
+
role: >
|
| 94 |
+
Analyste d'En-tête de CV
|
| 95 |
+
goal: >
|
| 96 |
+
Extraire le poste visé tel qu'il est écrit dans l'en-tête du CV (titre ou sous-titre juste après le nom du candidat).
|
| 97 |
+
backstory: >
|
| 98 |
+
Tu es un recruteur senior spécialisé dans l'analyse de CV tech/data/IA.
|
| 99 |
+
Ta mission PRINCIPALE est d'extraire le titre de poste EXACT tel qu'il apparaît dans l'en-tête du CV.
|
| 100 |
+
Le poste visé se trouve TOUJOURS dans le HEADER
|
| 101 |
+
(ex: "Data Analyst", "Chef de Projet IA", "Développeur Full-Stack - Spécialiste React").
|
| 102 |
+
Tu dois recopier ce titre FIDÈLEMENT, sans le simplifier, le reformuler, ni l'interpréter.
|
| 103 |
+
verbose: true
|
| 104 |
+
|
| 105 |
+
metier_matcher:
|
| 106 |
+
role: >
|
| 107 |
+
Conseiller en Orientation Professionnelle Data/IA
|
| 108 |
+
goal: >
|
| 109 |
+
Comparer le profil complet du candidat (compétences, projets, expériences, méthodologies)
|
| 110 |
+
avec le référentiel de métiers et recommander les 3 postes les mieux adaptés.
|
| 111 |
+
backstory: >
|
| 112 |
+
Tu es un expert en orientation professionnelle spécialisé dans les métiers de la data et de l'IA.
|
| 113 |
+
Tu connais parfaitement les fiches métiers du référentiel et tu sais évaluer objectivement
|
| 114 |
+
l'adéquation entre un profil et un poste. Tu comprends les liens implicites entre compétences
|
| 115 |
+
(ex: Metabase est un outil BI, LangChain est lié au LLM engineering).
|
| 116 |
+
Tu évalues la couverture des compétences techniques, des outils, de l'expérience requise,
|
| 117 |
+
ET des méthodologies de travail (Agile, Scrum, DevOps, CI/CD, TDD, Design Thinking).
|
| 118 |
+
Les méthodologies sont devenues un critère de sélection majeur dans le recrutement tech.
|
| 119 |
+
Pour les profils en reconversion, tu valorises les compétences transférables
|
| 120 |
+
(gestion d'équipe, planification, optimisation de processus, communication internationale).
|
| 121 |
+
Ton analyse est factuelle et basée sur des preuves concrètes du CV.
|
| 122 |
+
verbose: false
|
| 123 |
+
|
| 124 |
+
cv_quality_checker:
|
| 125 |
+
role: >
|
| 126 |
+
Auditeur de Qualité CV Tech
|
| 127 |
goal: >
|
| 128 |
+
Évaluer objectivement la qualité du CV selon les meilleures pratiques tech 2025,
|
| 129 |
+
en adaptant les critères au niveau de séniorité du candidat.
|
| 130 |
backstory: >
|
| 131 |
+
Tu es un consultant RH expert en recrutement tech et data.
|
| 132 |
+
Tu ADAPTES tes critères au NIVEAU DE SÉNIORITÉ du candidat :
|
| 133 |
+
- Junior : focus sur les projets, formations, stages/alternances bien décrits
|
| 134 |
+
- Confirmé : focus sur l'impact mesurable, la progression, les responsabilités croissantes
|
| 135 |
+
- Senior/Staff : focus sur les choix architecturaux et leurs compromis (systèmes distribués,
|
| 136 |
+
microservices), le leadership technique (mentoring, revues de code, décisions structurelles),
|
| 137 |
+
la gestion de la scalabilité
|
| 138 |
+
Tu exiges des MÉTRIQUES TECHNIQUES SPÉCIFIQUES : réduction de latence, amélioration du temps
|
| 139 |
+
de chargement, optimisation de requêtes, volume d'utilisateurs supporté, réduction du temps
|
| 140 |
+
de déploiement CI/CD, couverture de tests.
|
| 141 |
+
Tu vérifies que les compétences sont STRUCTURÉES par catégories (Langages, Frameworks, BDD,
|
| 142 |
+
DevOps/Cloud) et non en liste plate.
|
| 143 |
+
Tu vérifies que chaque compétence listée est RÉELLEMENT démontrée dans les expériences/projets.
|
| 144 |
+
Pour les RECONVERSIONS : tu vérifies la mise en valeur des compétences transférables
|
| 145 |
+
(management, optimisation, communication) et leur lien explicite avec le nouveau domaine.
|
| 146 |
verbose: false
|
| 147 |
+
|
| 148 |
+
project_analyzer:
|
| 149 |
+
role: >
|
| 150 |
+
Analyste de Projets Techniques & Conseiller en Mise en Avant
|
| 151 |
+
goal: >
|
| 152 |
+
Évaluer chaque projet et expérience du CV, fournir une critique objective et complète,
|
| 153 |
+
et recommander quels projets mettre en avant pour le poste visé.
|
| 154 |
+
backstory: >
|
| 155 |
+
Tu es un directeur technique (CTO) qui évalue les projets des candidats.
|
| 156 |
+
Pour chaque projet tu analyses :
|
| 157 |
+
1. La cohérence avec le poste visé ET avec le référentiel métier correspondant
|
| 158 |
+
2. La qualité de description (résultats mesurables, métriques techniques spécifiques)
|
| 159 |
+
3. La complexité technique (trivial vs ambitieux, architecture, choix techniques)
|
| 160 |
+
4. L'impact démontré (métriques, utilisateurs, déploiement, performance)
|
| 161 |
+
5. Les technologies utilisées (actuelles et recherchées pour le poste visé ?)
|
| 162 |
+
Tu fournis une RECOMMANDATION DE MISE EN AVANT : quels projets le candidat devrait
|
| 163 |
+
présenter en priorité pour le poste visé, et comment améliorer leur description.
|
| 164 |
+
Pour les profils en reconversion, tu identifies les compétences transférables
|
| 165 |
+
démontrées dans les expériences non-tech (gestion, optimisation, leadership, communication).
|
| 166 |
+
Tu analyses aussi les EXPÉRIENCES PROFESSIONNELLES pour identifier les compétences
|
| 167 |
+
transférables et les relier au poste visé.
|
| 168 |
+
Tu donnes des conseils CONCRETS et ACTIONNABLES pour chaque projet.
|
| 169 |
+
verbose: false
|
| 170 |
+
|
src/config/app_config.py
CHANGED
|
@@ -1,31 +1,62 @@
|
|
|
|
|
|
|
|
|
|
|
| 1 |
|
| 2 |
import os
|
| 3 |
from dotenv import load_dotenv
|
| 4 |
load_dotenv()
|
|
|
|
| 5 |
import pymupdf4llm
|
| 6 |
from langchain_groq import ChatGroq
|
| 7 |
from langchain_openai import ChatOpenAI
|
| 8 |
import litellm
|
| 9 |
litellm.set_verbose = False
|
| 10 |
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
|
| 15 |
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
|
| 16 |
GROQ_API_KEY = os.getenv("GROQ_API_KEY")
|
| 17 |
|
| 18 |
|
| 19 |
def get_big_llm():
|
| 20 |
-
"""GPT-4o pour les tâches complexes."""
|
| 21 |
return ChatOpenAI(
|
| 22 |
model="gpt-4o",
|
| 23 |
temperature=0.0,
|
|
|
|
| 24 |
api_key=OPENAI_API_KEY
|
| 25 |
)
|
| 26 |
|
|
|
|
| 27 |
def get_small_llm():
|
| 28 |
-
"""GPT-4o-mini pour l'extraction."""
|
| 29 |
return ChatOpenAI(
|
| 30 |
model="gpt-4o-mini",
|
| 31 |
temperature=0.0,
|
|
@@ -33,19 +64,12 @@ def get_small_llm():
|
|
| 33 |
api_key=OPENAI_API_KEY
|
| 34 |
)
|
| 35 |
|
|
|
|
| 36 |
def get_fast_llm():
|
| 37 |
-
"""Groq llama-3.1-8b - Le plus
|
| 38 |
return ChatGroq(
|
| 39 |
model="groq/llama-3.1-8b-instant",
|
| 40 |
temperature=0.0,
|
| 41 |
max_tokens=1500,
|
| 42 |
groq_api_key=GROQ_API_KEY
|
| 43 |
)
|
| 44 |
-
|
| 45 |
-
def get_openai_small_llm():
|
| 46 |
-
"""GPT-4o-mini - Fallback."""
|
| 47 |
-
return ChatOpenAI(
|
| 48 |
-
model="gpt-4o-mini",
|
| 49 |
-
temperature=0.0,
|
| 50 |
-
api_key=OPENAI_API_KEY
|
| 51 |
-
)
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Configuration des LLMs et utilitaires de chargement.
|
| 3 |
+
"""
|
| 4 |
|
| 5 |
import os
|
| 6 |
from dotenv import load_dotenv
|
| 7 |
load_dotenv()
|
| 8 |
+
import fitz
|
| 9 |
import pymupdf4llm
|
| 10 |
from langchain_groq import ChatGroq
|
| 11 |
from langchain_openai import ChatOpenAI
|
| 12 |
import litellm
|
| 13 |
litellm.set_verbose = False
|
| 14 |
|
| 15 |
+
|
| 16 |
+
def load_pdf(pdf_path: str) -> str:
|
| 17 |
+
"""Convertit un PDF en texte Markdown via pymupdf4llm (structure + formatage)."""
|
| 18 |
+
return pymupdf4llm.to_markdown(pdf_path)
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
def load_pdf_first_page_text(pdf_path: str) -> str:
|
| 22 |
+
"""Extrait le texte brut de la première page en ordre de lecture (haut → bas, gauche → droite).
|
| 23 |
+
|
| 24 |
+
Utilise fitz directement pour capturer les headers/sidebars que pymupdf4llm
|
| 25 |
+
peut ignorer ou réordonner sur les CV à mise en page complexe (bannières colorées,
|
| 26 |
+
colonnes, boîtes décoratives).
|
| 27 |
+
"""
|
| 28 |
+
doc = fitz.open(pdf_path)
|
| 29 |
+
if not doc:
|
| 30 |
+
return ""
|
| 31 |
+
|
| 32 |
+
page = doc[0]
|
| 33 |
+
# Récupère les blocs texte avec leurs coordonnées
|
| 34 |
+
blocks = page.get_text("blocks") # (x0, y0, x1, y1, text, block_no, block_type)
|
| 35 |
+
# Filtre les blocs texte (type 0) non vides
|
| 36 |
+
text_blocks = [b for b in blocks if b[6] == 0 and b[4].strip()]
|
| 37 |
+
# Trie par ligne (y arrondi à 10px pour gérer l'alignement imparfait), puis par colonne (x)
|
| 38 |
+
text_blocks.sort(key=lambda b: (round(b[1] / 10) * 10, b[0]))
|
| 39 |
+
|
| 40 |
+
doc.close()
|
| 41 |
+
return "\n".join(b[4].strip() for b in text_blocks)
|
| 42 |
+
|
| 43 |
|
| 44 |
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
|
| 45 |
GROQ_API_KEY = os.getenv("GROQ_API_KEY")
|
| 46 |
|
| 47 |
|
| 48 |
def get_big_llm():
|
| 49 |
+
"""GPT-4o pour les tâches complexes — max_tokens élevé pour éviter la troncature JSON."""
|
| 50 |
return ChatOpenAI(
|
| 51 |
model="gpt-4o",
|
| 52 |
temperature=0.0,
|
| 53 |
+
max_tokens=16384,
|
| 54 |
api_key=OPENAI_API_KEY
|
| 55 |
)
|
| 56 |
|
| 57 |
+
|
| 58 |
def get_small_llm():
|
| 59 |
+
"""GPT-4o-mini pour l'extraction rapide."""
|
| 60 |
return ChatOpenAI(
|
| 61 |
model="gpt-4o-mini",
|
| 62 |
temperature=0.0,
|
|
|
|
| 64 |
api_key=OPENAI_API_KEY
|
| 65 |
)
|
| 66 |
|
| 67 |
+
|
| 68 |
def get_fast_llm():
|
| 69 |
+
"""Groq llama-3.1-8b - Le plus rapide."""
|
| 70 |
return ChatGroq(
|
| 71 |
model="groq/llama-3.1-8b-instant",
|
| 72 |
temperature=0.0,
|
| 73 |
max_tokens=1500,
|
| 74 |
groq_api_key=GROQ_API_KEY
|
| 75 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/config/skill_domain_map.json
ADDED
|
@@ -0,0 +1,393 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"bi_reporting": [
|
| 3 |
+
"metabase",
|
| 4 |
+
"power bi",
|
| 5 |
+
"powerbi",
|
| 6 |
+
"tableau",
|
| 7 |
+
"looker",
|
| 8 |
+
"qlik",
|
| 9 |
+
"qlikview",
|
| 10 |
+
"qliksense",
|
| 11 |
+
"dax",
|
| 12 |
+
"mdx",
|
| 13 |
+
"olap",
|
| 14 |
+
"ssas",
|
| 15 |
+
"ssrs",
|
| 16 |
+
"ssis",
|
| 17 |
+
"crystal reports",
|
| 18 |
+
"microstrategy",
|
| 19 |
+
"data studio",
|
| 20 |
+
"google data studio",
|
| 21 |
+
"superset",
|
| 22 |
+
"redash",
|
| 23 |
+
"grafana",
|
| 24 |
+
"kibana",
|
| 25 |
+
"thoughtspot",
|
| 26 |
+
"domo",
|
| 27 |
+
"sap analytics cloud"
|
| 28 |
+
],
|
| 29 |
+
"data_engineering_platforms": [
|
| 30 |
+
"airflow",
|
| 31 |
+
"dbt",
|
| 32 |
+
"spark",
|
| 33 |
+
"pyspark",
|
| 34 |
+
"kafka",
|
| 35 |
+
"fivetran",
|
| 36 |
+
"talend",
|
| 37 |
+
"nifi",
|
| 38 |
+
"luigi",
|
| 39 |
+
"dagster",
|
| 40 |
+
"prefect",
|
| 41 |
+
"mage",
|
| 42 |
+
"databricks",
|
| 43 |
+
"stitch",
|
| 44 |
+
"matillion",
|
| 45 |
+
"informatica",
|
| 46 |
+
"pentaho",
|
| 47 |
+
"etl",
|
| 48 |
+
"elt",
|
| 49 |
+
"data pipeline",
|
| 50 |
+
"airbyte",
|
| 51 |
+
"meltano",
|
| 52 |
+
"glue",
|
| 53 |
+
"aws glue",
|
| 54 |
+
"emr",
|
| 55 |
+
"athena",
|
| 56 |
+
"synapse",
|
| 57 |
+
"azure data factory",
|
| 58 |
+
"adf"
|
| 59 |
+
],
|
| 60 |
+
"data_science_analysis": [
|
| 61 |
+
"excel",
|
| 62 |
+
"google sheets",
|
| 63 |
+
"pandas",
|
| 64 |
+
"numpy",
|
| 65 |
+
"matplotlib",
|
| 66 |
+
"seaborn",
|
| 67 |
+
"plotly",
|
| 68 |
+
"jupyter",
|
| 69 |
+
"notebook",
|
| 70 |
+
"sql",
|
| 71 |
+
"statistiques",
|
| 72 |
+
"statistics",
|
| 73 |
+
"r",
|
| 74 |
+
"stata",
|
| 75 |
+
"spss",
|
| 76 |
+
"sas",
|
| 77 |
+
"scipy",
|
| 78 |
+
"statsmodels",
|
| 79 |
+
"polars",
|
| 80 |
+
"dask",
|
| 81 |
+
"streamit",
|
| 82 |
+
"bokeh",
|
| 83 |
+
"altair"
|
| 84 |
+
],
|
| 85 |
+
"machine_learning_classic": [
|
| 86 |
+
"scikit-learn",
|
| 87 |
+
"sklearn",
|
| 88 |
+
"xgboost",
|
| 89 |
+
"lightgbm",
|
| 90 |
+
"catboost",
|
| 91 |
+
"mlflow",
|
| 92 |
+
"optuna",
|
| 93 |
+
"hyperopt",
|
| 94 |
+
"feature engineering",
|
| 95 |
+
"random forest",
|
| 96 |
+
"regression",
|
| 97 |
+
"classification",
|
| 98 |
+
"clustering",
|
| 99 |
+
"ml",
|
| 100 |
+
"machine learning",
|
| 101 |
+
"pca",
|
| 102 |
+
"svm",
|
| 103 |
+
"knn",
|
| 104 |
+
"ensemble learning",
|
| 105 |
+
"gradient boosting",
|
| 106 |
+
"time series",
|
| 107 |
+
"prophet",
|
| 108 |
+
"arima"
|
| 109 |
+
],
|
| 110 |
+
"deep_learning_frameworks": [
|
| 111 |
+
"tensorflow",
|
| 112 |
+
"pytorch",
|
| 113 |
+
"keras",
|
| 114 |
+
"jax",
|
| 115 |
+
"mxnet",
|
| 116 |
+
"fastai",
|
| 117 |
+
"onnx",
|
| 118 |
+
"tensorrt",
|
| 119 |
+
"cuda",
|
| 120 |
+
"cudnn",
|
| 121 |
+
"torchvision",
|
| 122 |
+
"torchaudio"
|
| 123 |
+
],
|
| 124 |
+
"nlp_llm_models": [
|
| 125 |
+
"transformers",
|
| 126 |
+
"huggingface",
|
| 127 |
+
"bert",
|
| 128 |
+
"gpt",
|
| 129 |
+
"gpt-3",
|
| 130 |
+
"gpt-4",
|
| 131 |
+
"gpt-4o",
|
| 132 |
+
"claude",
|
| 133 |
+
"llama",
|
| 134 |
+
"llama2",
|
| 135 |
+
"llama3",
|
| 136 |
+
"mistral",
|
| 137 |
+
"mixtral",
|
| 138 |
+
"gemini",
|
| 139 |
+
"palm",
|
| 140 |
+
"bert",
|
| 141 |
+
"roberta",
|
| 142 |
+
"t5",
|
| 143 |
+
"whisper",
|
| 144 |
+
"stable diffusion",
|
| 145 |
+
"midjourney",
|
| 146 |
+
"nlp",
|
| 147 |
+
"natural language processing",
|
| 148 |
+
"tokenization",
|
| 149 |
+
"embeddings",
|
| 150 |
+
"fine-tuning",
|
| 151 |
+
"lora",
|
| 152 |
+
"qlora",
|
| 153 |
+
"quantization"
|
| 154 |
+
],
|
| 155 |
+
"ai_orchestration_agents": [
|
| 156 |
+
"langchain",
|
| 157 |
+
"langgraph",
|
| 158 |
+
"crewai",
|
| 159 |
+
"autogen",
|
| 160 |
+
"haystack",
|
| 161 |
+
"llama-index",
|
| 162 |
+
"llamaindex",
|
| 163 |
+
"agents",
|
| 164 |
+
"autonomous agents",
|
| 165 |
+
"tool use",
|
| 166 |
+
"function calling",
|
| 167 |
+
"prompt engineering",
|
| 168 |
+
"rag",
|
| 169 |
+
"retrieval augmented generation",
|
| 170 |
+
"semantic search",
|
| 171 |
+
"agentic workflows"
|
| 172 |
+
],
|
| 173 |
+
"vector_databases": [
|
| 174 |
+
"pinecone",
|
| 175 |
+
"chromadb",
|
| 176 |
+
"weaviate",
|
| 177 |
+
"milvus",
|
| 178 |
+
"faiss",
|
| 179 |
+
"qdrant",
|
| 180 |
+
"elasticsearch",
|
| 181 |
+
"opensearch",
|
| 182 |
+
"pgvector",
|
| 183 |
+
"supabase vector",
|
| 184 |
+
"redis stack",
|
| 185 |
+
"lancedb",
|
| 186 |
+
"marqo"
|
| 187 |
+
],
|
| 188 |
+
"cloud_ai_platforms": [
|
| 189 |
+
"openai",
|
| 190 |
+
"anthropic",
|
| 191 |
+
"google cloud vertex ai",
|
| 192 |
+
"vertex ai",
|
| 193 |
+
"aws bedrock",
|
| 194 |
+
"bedrock",
|
| 195 |
+
"azure openai",
|
| 196 |
+
"sagemaker",
|
| 197 |
+
"aws sagemaker",
|
| 198 |
+
"replicate",
|
| 199 |
+
"together ai",
|
| 200 |
+
"groq",
|
| 201 |
+
"perplexity"
|
| 202 |
+
],
|
| 203 |
+
"backend_frameworks": [
|
| 204 |
+
"fastapi",
|
| 205 |
+
"django",
|
| 206 |
+
"flask",
|
| 207 |
+
"express",
|
| 208 |
+
"nestjs",
|
| 209 |
+
"spring",
|
| 210 |
+
"spring boot",
|
| 211 |
+
"node.js",
|
| 212 |
+
"nodejs",
|
| 213 |
+
"ruby on rails",
|
| 214 |
+
"laravel",
|
| 215 |
+
"symfony",
|
| 216 |
+
"go",
|
| 217 |
+
"golang",
|
| 218 |
+
"gin",
|
| 219 |
+
"fiber",
|
| 220 |
+
"elixir",
|
| 221 |
+
"phoenix",
|
| 222 |
+
"api rest",
|
| 223 |
+
"graphql",
|
| 224 |
+
"grpc",
|
| 225 |
+
"microservices",
|
| 226 |
+
"serverless"
|
| 227 |
+
],
|
| 228 |
+
"frontend_web": [
|
| 229 |
+
"react",
|
| 230 |
+
"reactjs",
|
| 231 |
+
"vue",
|
| 232 |
+
"vuejs",
|
| 233 |
+
"angular",
|
| 234 |
+
"nextjs",
|
| 235 |
+
"next.js",
|
| 236 |
+
"nuxt",
|
| 237 |
+
"svelte",
|
| 238 |
+
"typescript",
|
| 239 |
+
"javascript",
|
| 240 |
+
"html",
|
| 241 |
+
"css",
|
| 242 |
+
"tailwind",
|
| 243 |
+
"tailwind css",
|
| 244 |
+
"bootstrap",
|
| 245 |
+
"sass",
|
| 246 |
+
"webpack",
|
| 247 |
+
"vite",
|
| 248 |
+
"three.js",
|
| 249 |
+
"d3.js",
|
| 250 |
+
"redux",
|
| 251 |
+
"zustand",
|
| 252 |
+
"react-query",
|
| 253 |
+
"tanstack"
|
| 254 |
+
],
|
| 255 |
+
"mobile_development": [
|
| 256 |
+
"react native",
|
| 257 |
+
"flutter",
|
| 258 |
+
"swift",
|
| 259 |
+
"swiftui",
|
| 260 |
+
"kotlin",
|
| 261 |
+
"jetpack compose",
|
| 262 |
+
"ios",
|
| 263 |
+
"android",
|
| 264 |
+
"expo",
|
| 265 |
+
"capacitor",
|
| 266 |
+
"ionic",
|
| 267 |
+
"pwa"
|
| 268 |
+
],
|
| 269 |
+
"devops_infrastructure": [
|
| 270 |
+
"docker",
|
| 271 |
+
"kubernetes",
|
| 272 |
+
"k8s",
|
| 273 |
+
"terraform",
|
| 274 |
+
"ansible",
|
| 275 |
+
"pulumi",
|
| 276 |
+
"chef",
|
| 277 |
+
"puppet",
|
| 278 |
+
"cloudformation",
|
| 279 |
+
"helm",
|
| 280 |
+
"nginx",
|
| 281 |
+
"traefik",
|
| 282 |
+
"envoy",
|
| 283 |
+
"istio",
|
| 284 |
+
"server management",
|
| 285 |
+
"linux",
|
| 286 |
+
"bash",
|
| 287 |
+
"powershell"
|
| 288 |
+
],
|
| 289 |
+
"ci_cd_pipelines": [
|
| 290 |
+
"jenkins",
|
| 291 |
+
"github actions",
|
| 292 |
+
"gitlab ci",
|
| 293 |
+
"circleci",
|
| 294 |
+
"travis ci",
|
| 295 |
+
"argocd",
|
| 296 |
+
"fluxcd",
|
| 297 |
+
"ci/cd",
|
| 298 |
+
"cicd",
|
| 299 |
+
"automation",
|
| 300 |
+
"deployment"
|
| 301 |
+
],
|
| 302 |
+
"cloud_providers": [
|
| 303 |
+
"aws",
|
| 304 |
+
"amazon web services",
|
| 305 |
+
"azure",
|
| 306 |
+
"microsoft azure",
|
| 307 |
+
"gcp",
|
| 308 |
+
"google cloud",
|
| 309 |
+
"google cloud platform",
|
| 310 |
+
"firebase",
|
| 311 |
+
"vercel",
|
| 312 |
+
"heroku",
|
| 313 |
+
"railway",
|
| 314 |
+
"digitalocean",
|
| 315 |
+
"cloudinary",
|
| 316 |
+
"supabase",
|
| 317 |
+
"lambda",
|
| 318 |
+
"s3",
|
| 319 |
+
"ec2",
|
| 320 |
+
"vpc"
|
| 321 |
+
],
|
| 322 |
+
"database_systems": [
|
| 323 |
+
"postgresql",
|
| 324 |
+
"postgres",
|
| 325 |
+
"mysql",
|
| 326 |
+
"mariadb",
|
| 327 |
+
"mongodb",
|
| 328 |
+
"redis",
|
| 329 |
+
"memcached",
|
| 330 |
+
"cassandra",
|
| 331 |
+
"dynamodb",
|
| 332 |
+
"neo4j",
|
| 333 |
+
"firestore",
|
| 334 |
+
"bigquery",
|
| 335 |
+
"snowflake",
|
| 336 |
+
"redshift",
|
| 337 |
+
"clickhouse",
|
| 338 |
+
"sqlite",
|
| 339 |
+
"oracle",
|
| 340 |
+
"sql server",
|
| 341 |
+
"db2"
|
| 342 |
+
],
|
| 343 |
+
"cybersecurity": [
|
| 344 |
+
"owasp",
|
| 345 |
+
"sast",
|
| 346 |
+
"dast",
|
| 347 |
+
"pentest",
|
| 348 |
+
"penetration testing",
|
| 349 |
+
"cybersecurity",
|
| 350 |
+
"security audit",
|
| 351 |
+
"cryptographie",
|
| 352 |
+
"cryptography",
|
| 353 |
+
"oauth",
|
| 354 |
+
"oauth2",
|
| 355 |
+
"jwt",
|
| 356 |
+
"ssl",
|
| 357 |
+
"tls",
|
| 358 |
+
"iam",
|
| 359 |
+
"sso",
|
| 360 |
+
"ldap",
|
| 361 |
+
"keycloak",
|
| 362 |
+
"waf",
|
| 363 |
+
"siem",
|
| 364 |
+
"soc"
|
| 365 |
+
],
|
| 366 |
+
"project_management_agile": [
|
| 367 |
+
"agile",
|
| 368 |
+
"scrum",
|
| 369 |
+
"kanban",
|
| 370 |
+
"jira",
|
| 371 |
+
"confluence",
|
| 372 |
+
"notion",
|
| 373 |
+
"trello",
|
| 374 |
+
"monday",
|
| 375 |
+
"linear",
|
| 376 |
+
"asana",
|
| 377 |
+
"slack",
|
| 378 |
+
"product manager",
|
| 379 |
+
"product owner",
|
| 380 |
+
"scrum master",
|
| 381 |
+
"lean management",
|
| 382 |
+
"design thinking",
|
| 383 |
+
"figma",
|
| 384 |
+
"miro"
|
| 385 |
+
],
|
| 386 |
+
"automatisation": [
|
| 387 |
+
"n8n",
|
| 388 |
+
"gumloop",
|
| 389 |
+
"make",
|
| 390 |
+
"zapier",
|
| 391 |
+
"power automate"
|
| 392 |
+
]
|
| 393 |
+
}
|
src/config/tasks.yaml
CHANGED
|
@@ -1,30 +1,34 @@
|
|
|
|
|
|
|
|
| 1 |
split_cv_task:
|
| 2 |
description: >
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
TEXTE DU CV :
|
| 8 |
"{cv_content}"
|
| 9 |
|
|
|
|
|
|
|
|
|
|
| 10 |
RÈGLES STRICTES :
|
| 11 |
-
1. "
|
| 12 |
-
2. "
|
| 13 |
-
3. "
|
| 14 |
-
4. "
|
| 15 |
-
5. "
|
| 16 |
-
6. "
|
| 17 |
expected_output: >
|
| 18 |
Un objet JSON valide strictement structuré ainsi :
|
| 19 |
{{
|
|
|
|
| 20 |
"experiences": "texte brut...",
|
| 21 |
"projects": "texte brut...",
|
| 22 |
"education": "texte brut...",
|
| 23 |
"skills": "texte brut...",
|
| 24 |
-
"languages": "texte brut..."
|
| 25 |
-
"personal_info": "texte brut..."
|
| 26 |
}}
|
| 27 |
|
|
|
|
| 28 |
|
| 29 |
skills_task:
|
| 30 |
description: >
|
|
@@ -123,23 +127,272 @@ etudiant_task:
|
|
| 123 |
|
| 124 |
language_task:
|
| 125 |
description: >
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
|
|
|
|
|
|
|
|
|
| 129 |
RÈGLES :
|
| 130 |
-
1.
|
| 131 |
-
2.
|
|
|
|
|
|
|
| 132 |
expected_output: >
|
| 133 |
-
JSON : {{"langues": [{{"langue": "Anglais"}}]}}
|
| 134 |
|
| 135 |
identity_task:
|
| 136 |
description: >
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 140 |
RÈGLES :
|
| 141 |
-
1.
|
| 142 |
-
2.
|
| 143 |
-
3.
|
|
|
|
| 144 |
expected_output: >
|
| 145 |
-
JSON : {{"first_name": "
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# === CV splitter ===
|
| 2 |
+
|
| 3 |
split_cv_task:
|
| 4 |
description: >
|
| 5 |
+
Découpe le CV en sections JSON. Copie le texte brut sans reformuler ni résumer.
|
| 6 |
+
|
| 7 |
+
TEXTE PRINCIPAL DU CV (Markdown) :
|
|
|
|
|
|
|
| 8 |
"{cv_content}"
|
| 9 |
|
| 10 |
+
TEXTE BRUT PREMIÈRE PAGE (extraction directe, utilise-le si le texte principal manque le header) :
|
| 11 |
+
"{cv_raw_start}"
|
| 12 |
+
|
| 13 |
RÈGLES STRICTES :
|
| 14 |
+
1. "header" : Les premières lignes du CV — contient le NOM du candidat, son TITRE/POSTE, ses coordonnées (email, téléphone, LinkedIn, ville). Cherche en priorité dans le TEXTE BRUT PREMIÈRE PAGE car le Markdown peut mal ordonner le header.
|
| 15 |
+
2. "experiences" : Uniquement l'historique professionnel (Entreprise, Poste, Dates, missions).
|
| 16 |
+
3. "projects" : Sections explicitement titrées "Projets". Laisser VIDE si absent, ne pas inventer.
|
| 17 |
+
4. "skills" : Listes de compétences, langages, outils.
|
| 18 |
+
5. "education" : Diplômes et formations.
|
| 19 |
+
6. "languages" : Langues mentionnées avec leur niveau (Français, Anglais, etc.).
|
| 20 |
expected_output: >
|
| 21 |
Un objet JSON valide strictement structuré ainsi :
|
| 22 |
{{
|
| 23 |
+
"header": "texte brut du header (nom, titre, contact)...",
|
| 24 |
"experiences": "texte brut...",
|
| 25 |
"projects": "texte brut...",
|
| 26 |
"education": "texte brut...",
|
| 27 |
"skills": "texte brut...",
|
| 28 |
+
"languages": "texte brut..."
|
|
|
|
| 29 |
}}
|
| 30 |
|
| 31 |
+
# === Tache d'extractions des informations ===
|
| 32 |
|
| 33 |
skills_task:
|
| 34 |
description: >
|
|
|
|
| 127 |
|
| 128 |
language_task:
|
| 129 |
description: >
|
| 130 |
+
Identifie toutes les langues parlées par le candidat.
|
| 131 |
+
|
| 132 |
+
SECTION LANGUES (extraite) : "{languages}"
|
| 133 |
+
|
| 134 |
+
DÉBUT DU CV (pour détecter la langue de rédaction) : "{cv_raw_start}"
|
| 135 |
+
|
| 136 |
RÈGLES :
|
| 137 |
+
1. Extrais toutes les langues et niveaux présents dans la SECTION LANGUES.
|
| 138 |
+
2. Détecte la langue dans laquelle le CV est rédigé à partir du DÉBUT DU CV.
|
| 139 |
+
3. Si la langue du CV n'est PAS dans la SECTION LANGUES, ajoute-la avec le niveau "Natif" ou "Langue maternelle".
|
| 140 |
+
4. Ne jamais omettre la langue du CV.
|
| 141 |
expected_output: >
|
| 142 |
+
JSON : {{"langues": [{{"langue": "Français", "niveau": "Natif"}}, {{"langue": "Anglais", "niveau": "B2"}}]}}
|
| 143 |
|
| 144 |
identity_task:
|
| 145 |
description: >
|
| 146 |
+
Extrais le prénom du candidat.
|
| 147 |
+
|
| 148 |
+
HEADER DU CV (nom, titre, contact) : "{header}"
|
| 149 |
+
|
| 150 |
+
TEXTE BRUT DÉBUT DU CV : "{cv_raw_start}"
|
| 151 |
+
|
| 152 |
+
NOM DU FICHIER (indice très fiable, souvent au format NOM_PRENOM_...) : "{file_name}"
|
| 153 |
+
|
| 154 |
RÈGLES :
|
| 155 |
+
1. Cherche le prénom dans le HEADER, puis dans le TEXTE BRUT DÉBUT DU CV.
|
| 156 |
+
2. Le NOM DU FICHIER est un indice fort : "ANISSA_KACEM_..." → prénom = "Anissa".
|
| 157 |
+
3. Ne jamais inventer. Formate avec majuscule initiale.
|
| 158 |
+
4. Si impossible à trouver, retourne null.
|
| 159 |
expected_output: >
|
| 160 |
+
JSON : {{"first_name": "..."}}
|
| 161 |
+
|
| 162 |
+
poste_visé_task:
|
| 163 |
+
description: >
|
| 164 |
+
Extrais le titre de poste visé tel qu'il est écrit dans l'en-tête du CV.
|
| 165 |
+
|
| 166 |
+
HEADER DU CV (extrait par le splitter) : "{header}"
|
| 167 |
+
|
| 168 |
+
TEXTE BRUT DÉBUT DU CV (fallback si header vide) : "{cv_raw_start}"
|
| 169 |
+
|
| 170 |
+
RÈGLES :
|
| 171 |
+
1. Le titre de poste se trouve juste après le nom du candidat (ex: "Business Analyst", "Data Engineer").
|
| 172 |
+
2. Copie le titre EXACTEMENT tel qu'il est écrit, sans reformuler.
|
| 173 |
+
3. Si le header est vide, cherche dans le TEXTE BRUT DÉBUT DU CV.
|
| 174 |
+
4. Ne jamais inventer un titre.
|
| 175 |
+
expected_output: >
|
| 176 |
+
JSON : {{
|
| 177 |
+
"poste_vise": "Le titre EXACT tel qu'écrit sur le CV",
|
| 178 |
+
"confiance": 90
|
| 179 |
+
}}
|
| 180 |
+
|
| 181 |
+
# === partie matching ===
|
| 182 |
+
|
| 183 |
+
metier_matching_task:
|
| 184 |
+
description: >
|
| 185 |
+
Compare le profil du candidat avec le référentiel de métiers pour recommander les 3 postes les plus adaptés.
|
| 186 |
+
|
| 187 |
+
POSTE VISÉ PAR LE CANDIDAT : "{poste_vise}"
|
| 188 |
+
|
| 189 |
+
COMPÉTENCES DU CANDIDAT :
|
| 190 |
+
Hard Skills : {hard_skills}
|
| 191 |
+
Soft Skills : {soft_skills}
|
| 192 |
+
|
| 193 |
+
DOMAINES DE COMPÉTENCES IDENTIFIÉS : {skill_domains}
|
| 194 |
+
|
| 195 |
+
MÉTHODOLOGIES DU CANDIDAT : {methodologies}
|
| 196 |
+
|
| 197 |
+
EXPÉRIENCES : {experiences_summary}
|
| 198 |
+
|
| 199 |
+
PROJETS : {projects_summary}
|
| 200 |
+
|
| 201 |
+
RECONVERSION : {reconversion_data}
|
| 202 |
+
|
| 203 |
+
RÉFÉRENTIEL DE MÉTIERS :
|
| 204 |
+
{metiers_reference}
|
| 205 |
+
|
| 206 |
+
RÈGLES D'ANALYSE :
|
| 207 |
+
IMPORTANT : Tu dois évaluer CHAQUE métier présent dans le RÉFÉRENTIEL DE MÉTIERS ci-dessus,
|
| 208 |
+
sans en omettre aucun. Le top 3 final doit être basé sur l'évaluation exhaustive de tous
|
| 209 |
+
les métiers listés. Ne jamais présélectionner ou ignorer des métiers a priori.
|
| 210 |
+
|
| 211 |
+
1. Pour CHAQUE métier du référentiel, calcule un score de matching (0-100) basé sur :
|
| 212 |
+
- Couverture des compétences techniques requises (35%)
|
| 213 |
+
- Couverture des outils/technologies (25%)
|
| 214 |
+
- Adéquation des expériences et projets (20%)
|
| 215 |
+
- Maîtrise des méthodologies de travail : Agile, Scrum, DevOps, CI/CD, TDD, Design Thinking (10%)
|
| 216 |
+
- Cohérence avec le niveau d'études et l'expérience requise (10%)
|
| 217 |
+
2. Utilise le mapping de domaines pour comprendre les liens implicites (ex: Metabase → BI,
|
| 218 |
+
LangChain → LLM Engineering, Power BI → BI Analyst, Scikit-learn → Data Science).
|
| 219 |
+
3. Pour les profils en reconversion, valorise les compétences transférables
|
| 220 |
+
(gestion d'équipe → leadership, optimisation de production → optimisation de processus data,
|
| 221 |
+
communication internationale → travail en équipe multiculturelle).
|
| 222 |
+
4. Recommande les 3 métiers avec le MEILLEUR score parmi l'ensemble du référentiel évalué.
|
| 223 |
+
5. PONDÉRATION TEMPORELLE (CRITIQUE) : Accorde un poids double (x2) aux technologies et compétences issues des expériences et projets les plus récents, ainsi qu'à la formation en cours. Le profil actuel d'un candidat est défini par ce qu'il fait aujourd'hui, pas par son historique lointain.
|
| 224 |
+
6. Pour chaque métier recommandé, liste les compétences matchées, manquantes, et les méthodologies.
|
| 225 |
+
7. Si le poste visé par le candidat ne fait pas partie du top 3, explique pourquoi.
|
| 226 |
+
8. Fournis une analyse détaillée de l'adéquation du poste visé avec le profil.
|
| 227 |
+
expected_output: >
|
| 228 |
+
JSON : {{
|
| 229 |
+
"postes_recommandes": [
|
| 230 |
+
{{
|
| 231 |
+
"metier_id": "data_analyst",
|
| 232 |
+
"nom": "Data Analyst",
|
| 233 |
+
"categorie": "Noyau data & analytique",
|
| 234 |
+
"score_matching": 85,
|
| 235 |
+
"detail_scores": {{
|
| 236 |
+
"competences_techniques": 80,
|
| 237 |
+
"outils_technologies": 90,
|
| 238 |
+
"experiences_projets": 85,
|
| 239 |
+
"methodologies": 75
|
| 240 |
+
}},
|
| 241 |
+
"competences_matchees": ["SQL", "Python", "Power BI"],
|
| 242 |
+
"competences_manquantes": ["Looker", "dbt"],
|
| 243 |
+
"methodologies_matchees": ["Agile", "Scrum"],
|
| 244 |
+
"justification": "Le profil couvre 85% des compétences requises..."
|
| 245 |
+
}}
|
| 246 |
+
],
|
| 247 |
+
"poste_vise_dans_top3": true,
|
| 248 |
+
"analyse_poste_vise": "Analyse détaillée de l'adéquation..."
|
| 249 |
+
}}
|
| 250 |
+
|
| 251 |
+
cv_quality_task:
|
| 252 |
+
description: >
|
| 253 |
+
Évalue la qualité globale du CV en appliquant les critères de bonnes pratiques CV tech 2025,
|
| 254 |
+
adaptés au niveau de séniorité du candidat.
|
| 255 |
+
|
| 256 |
+
CV COMPLET (texte Markdown) : "{cv_full_text}"
|
| 257 |
+
|
| 258 |
+
TEXTE BRUT DU CV (première page, pour détecter les URLs et liens) : "{cv_raw_start}"
|
| 259 |
+
|
| 260 |
+
COMPÉTENCES EXTRAITES AVEC CONTEXTE : {skills_with_context}
|
| 261 |
+
EXPÉRIENCES : {experiences_summary}
|
| 262 |
+
PROJETS : {projects_summary}
|
| 263 |
+
NIVEAU DE SÉNIORITÉ : "{niveau_seniorite}"
|
| 264 |
+
RECONVERSION : {reconversion_data}
|
| 265 |
+
|
| 266 |
+
CRITÈRES D'ÉVALUATION (score sur 100 pour chaque) :
|
| 267 |
+
|
| 268 |
+
1. COMPATIBILITÉ ATS (20 points) :
|
| 269 |
+
- Structure claire avec sections standards ?
|
| 270 |
+
- Pas de mise en page complexe qui bloquerait un ATS ?
|
| 271 |
+
- Mots-clés techniques bien présents ?
|
| 272 |
+
|
| 273 |
+
2. QUANTIFICATION DES RÉSULTATS (25 points) :
|
| 274 |
+
- Les expériences mentionnent-elles des MÉTRIQUES TECHNIQUES SPÉCIFIQUES ?
|
| 275 |
+
Cherche : réduction de latence (ms), amélioration du temps de chargement (%),
|
| 276 |
+
optimisation de requêtes SQL (x fois plus rapide), volume d'utilisateurs supporté,
|
| 277 |
+
réduction du temps de déploiement CI/CD, couverture de tests (%),
|
| 278 |
+
réduction des coûts d'infrastructure, nombre de pipelines automatisés,
|
| 279 |
+
volume de données traité, temps de réponse des APIs.
|
| 280 |
+
- Donne des suggestions SPÉCIFIQUES de métriques que le candidat pourrait ajouter
|
| 281 |
+
en fonction de ses expériences et projets RÉELS (pas des conseils génériques).
|
| 282 |
+
|
| 283 |
+
3. STRUCTURE ET LISIBILITÉ (15 points) :
|
| 284 |
+
- Le CV tient-il en 1-2 pages ?
|
| 285 |
+
- Les sections sont-elles bien séparées et la chronologie claire ?
|
| 286 |
+
- STRUCTURATION DES COMPÉTENCES : Les compétences sont-elles regroupées par catégories
|
| 287 |
+
logiques (Langages, Frameworks, BDD, DevOps/Cloud, Méthodologies) ou en liste plate ?
|
| 288 |
+
Une structuration par catégories est fortement recommandée pour les filtres ATS.
|
| 289 |
+
|
| 290 |
+
4. PRÉSENTATION DES PROJETS (20 points) :
|
| 291 |
+
- Chaque projet a-t-il un titre, des technos, et des résultats ?
|
| 292 |
+
- Les projets sont-ils pertinents pour le poste visé ?
|
| 293 |
+
- Y a-t-il une variété de projets (pro + perso) ?
|
| 294 |
+
|
| 295 |
+
5. PREUVES DE COMPÉTENCES (20 points) :
|
| 296 |
+
- RÈGLE CRITIQUE : Une compétence est considérée "sans preuve" UNIQUEMENT si elle
|
| 297 |
+
apparaît EXCLUSIVEMENT dans la section Skills/Compétences sans aucune mention dans
|
| 298 |
+
les expériences OU les projets. Utilise "skills_with_context" : si le contexte est
|
| 299 |
+
"projet", "expérience", "académique" ou "projet+expérience", la compétence EST prouvée —
|
| 300 |
+
ne la signale pas. Ne signale que les skills dont le contexte est "sans contexte".
|
| 301 |
+
- DÉTECTION DES LIENS : Cherche les URLs dans le CV Markdown ET dans le texte brut.
|
| 302 |
+
Les liens peuvent apparaître sous forme de Markdown [texte](url), de texte brut
|
| 303 |
+
(github.com/..., linkedin.com/...) ou dans le header. Signale les liens PRÉSENTS,
|
| 304 |
+
ne jamais conclure à l'absence de liens sans avoir vérifié les deux sources de texte.
|
| 305 |
+
- Pour les RECONVERSIONS : les compétences transférables (management, optimisation,
|
| 306 |
+
communication, gestion budgétaire) sont-elles mises en valeur et reliées au nouveau domaine ?
|
| 307 |
+
|
| 308 |
+
ADAPTATION AU NIVEAU DE SÉNIORITÉ "{niveau_seniorite}" :
|
| 309 |
+
- Si JUNIOR : valorise les projets personnels, formations, stages bien décrits.
|
| 310 |
+
- Si CONFIRMÉ : exige des résultats mesurables, progression, responsabilités.
|
| 311 |
+
- Si SENIOR/STAFF : vérifie la présence de choix architecturaux et compromis
|
| 312 |
+
(systèmes distribués, microservices), leadership technique (mentoring, revues de code),
|
| 313 |
+
gestion de la scalabilité, impact organisationnel au-delà du code.
|
| 314 |
+
|
| 315 |
+
RED FLAGS À DÉTECTER :
|
| 316 |
+
- Skills listées UNIQUEMENT dans la section skills sans aucune mention dans expériences/projets
|
| 317 |
+
- Trous inexpliqués dans le parcours
|
| 318 |
+
- Jargon excessif ou buzzwords sans substance
|
| 319 |
+
- Incohérence entre compétences listées et projets/expériences
|
| 320 |
+
- Section compétences en liste plate non catégorisée
|
| 321 |
+
expected_output: >
|
| 322 |
+
JSON : {{
|
| 323 |
+
"score_global": 72,
|
| 324 |
+
"compatibilite_ats": {{ "score": 80, "details": "..." }},
|
| 325 |
+
"quantification_resultats": {{ "score": 50, "details": "...", "metriques_suggerees": ["Préciser le temps de réponse des dashboards PowerBI", "Quantifier le volume de données traité par les flows Dataiku"] }},
|
| 326 |
+
"structure_lisibilite": {{ "score": 85, "details": "...", "structuration_competences": "Compétences bien catégorisées par domaine" }},
|
| 327 |
+
"presentation_projets": {{ "score": 70, "details": "..." }},
|
| 328 |
+
"preuves_competences": {{ "score": 65, "details": "...", "skills_sans_preuve": ["skill_hors_contexte_uniquement"], "liens_detectes": ["github.com/user", "linkedin.com/in/user"] }},
|
| 329 |
+
"red_flags": ["..."],
|
| 330 |
+
"points_forts": ["..."],
|
| 331 |
+
"conseils_prioritaires": ["Conseil spécifique et actionnable 1", "Conseil spécifique 2"],
|
| 332 |
+
"adaptation_seniorite": "Analyse adaptée au profil confirmé..."
|
| 333 |
+
}}
|
| 334 |
+
|
| 335 |
+
project_analysis_task:
|
| 336 |
+
description: >
|
| 337 |
+
Évalue CHAQUE projet du CV, fournis une critique objective et complète,
|
| 338 |
+
et recommande quels projets mettre en avant pour le poste visé.
|
| 339 |
+
|
| 340 |
+
POSTE VISÉ : "{poste_vise}"
|
| 341 |
+
|
| 342 |
+
RÉFÉRENTIEL DU MÉTIER VISÉ (compétences et outils attendus) :
|
| 343 |
+
{metier_reference_detail}
|
| 344 |
+
|
| 345 |
+
EXPÉRIENCES DU CANDIDAT : {experiences_summary}
|
| 346 |
+
|
| 347 |
+
PROJETS PROFESSIONNELS : {professional_projects}
|
| 348 |
+
PROJETS PERSONNELS : {personal_projects}
|
| 349 |
+
|
| 350 |
+
RECONVERSION : {reconversion_data}
|
| 351 |
+
|
| 352 |
+
Pour CHAQUE projet, analyse EN PROFONDEUR :
|
| 353 |
+
1. COHÉRENCE AVEC LE POSTE VISÉ : Le domaine et les technos sont-ils pertinents ?
|
| 354 |
+
Compare avec les compétences et outils du référentiel métier ci-dessus.
|
| 355 |
+
2. QUALITÉ DE DESCRIPTION : Est-ce bien décrit ? Y a-t-il des résultats MESURABLES
|
| 356 |
+
et des métriques techniques spécifiques (performance, volume, impact) ?
|
| 357 |
+
3. COMPLEXITÉ TECHNIQUE : Trivial vs ambitieux. Évalue l'architecture, les choix techniques.
|
| 358 |
+
4. IMPACT DÉMONTRÉ : Métriques, utilisateurs, déploiement en production ?
|
| 359 |
+
5. TECHNOLOGIES : Actuelles et recherchées pour le poste visé ?
|
| 360 |
+
|
| 361 |
+
RECOMMANDATION DE MISE EN AVANT :
|
| 362 |
+
- Classe les projets par ORDRE DE PRIORITÉ pour le poste visé.
|
| 363 |
+
- Pour chaque projet, explique POURQUOI il devrait être mis en avant (ou pas) pour ce poste.
|
| 364 |
+
- Donne des conseils CONCRETS pour améliorer la description de chaque projet
|
| 365 |
+
(quelles métriques ajouter, quels aspects techniques détailler, quels résultats valoriser).
|
| 366 |
+
|
| 367 |
+
RÈGLES :
|
| 368 |
+
- Score de cohérence de 0 à 100 pour chaque projet.
|
| 369 |
+
- Si un projet semble artificiel ou trop vague, signale-le.
|
| 370 |
+
- Les projets doivent raconter une histoire cohérente avec le profil global.
|
| 371 |
+
expected_output: >
|
| 372 |
+
JSON : {{
|
| 373 |
+
"analyse_projets": [
|
| 374 |
+
{{
|
| 375 |
+
"titre": "Dashboard RH",
|
| 376 |
+
"type": "professional",
|
| 377 |
+
"score_coherence": 90,
|
| 378 |
+
"points_forts": ["Technologies pertinentes", "Impact mesurable"],
|
| 379 |
+
"points_amelioration": ["Ajouter des métriques de performance spécifiques"],
|
| 380 |
+
"coherence_avec_poste_vise": "Très cohérent - projet BI directement lié au poste",
|
| 381 |
+
"technologies_pertinentes": true,
|
| 382 |
+
"complexite": "moyenne",
|
| 383 |
+
"conseils_description": ["Préciser le volume de données", "Ajouter le temps de génération"]
|
| 384 |
+
}}
|
| 385 |
+
],
|
| 386 |
+
"ordre_mise_en_avant": [
|
| 387 |
+
{{
|
| 388 |
+
"titre": "Projet X",
|
| 389 |
+
"rang": 1,
|
| 390 |
+
"raison": "Ce projet démontre directement les compétences clés du poste visé..."
|
| 391 |
+
}}
|
| 392 |
+
],
|
| 393 |
+
"coherence_globale": {{
|
| 394 |
+
"score": 85,
|
| 395 |
+
"commentaire": "Les projets racontent une histoire cohérente..."
|
| 396 |
+
}}
|
| 397 |
+
}}
|
| 398 |
+
|
src/data/metiers.json
ADDED
|
@@ -0,0 +1,1273 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"metadata": {
|
| 3 |
+
"version": "2.0",
|
| 4 |
+
"date": "2026-02-06",
|
| 5 |
+
"langue": "fr",
|
| 6 |
+
"description": "Métiers Data/IA avec compétences et outils enrichis 2025-2026"
|
| 7 |
+
},
|
| 8 |
+
"metiers": [
|
| 9 |
+
{
|
| 10 |
+
"id": "data_analyst",
|
| 11 |
+
"nom": "Data Analyst",
|
| 12 |
+
"categorie": "Noyau data & analytique",
|
| 13 |
+
"description": "Analyse les données pour produire des rapports, tableaux de bord et recommandations opérationnelles au service des métiers.",
|
| 14 |
+
"missions_principales": [
|
| 15 |
+
"Collecter, nettoyer et structurer les données issues de différentes sources",
|
| 16 |
+
"Construire et maintenir des tableaux de bord et rapports réguliers",
|
| 17 |
+
"Analyser les tendances, identifier des anomalies et opportunités",
|
| 18 |
+
"Traduire les besoins métier en indicateurs et analyses",
|
| 19 |
+
"Présenter les résultats et recommandations aux équipes métier"
|
| 20 |
+
],
|
| 21 |
+
"competences_techniques": [
|
| 22 |
+
"Solide maîtrise de SQL (jointures, agrégations, CTE, window functions de base)",
|
| 23 |
+
"Bonne maîtrise d’Excel/Google Sheets (formules avancées, tableaux croisés dynamiques, macros simples)",
|
| 24 |
+
"Visualisation de données avec un ou plusieurs outils BI",
|
| 25 |
+
"Manipulation et préparation de données avec Python (pandas) ou R pour les profils plus techniques",
|
| 26 |
+
"Compréhension des bases de données relationnelles et schémas classiques (étoile, snowflake)",
|
| 27 |
+
"Notions de statistiques descriptives (moyenne, médiane, variance, corrélations, tests simples)",
|
| 28 |
+
"Connaissance de base des data warehouses et data lakes",
|
| 29 |
+
"Bonnes pratiques de requêtage (performance, lisibilité, documentation)",
|
| 30 |
+
"Notions d’automatisation des rapports (scheduling, rafraîchissement des datasets)",
|
| 31 |
+
"Capacité à interpréter les résultats d’AB testing ou d’expérimentations simples"
|
| 32 |
+
],
|
| 33 |
+
"outils_technologies": [
|
| 34 |
+
"SQL (PostgreSQL, MySQL, SQL Server, BigQuery, Snowflake)",
|
| 35 |
+
"Excel, Google Sheets",
|
| 36 |
+
"BI : Power BI, Tableau, Looker, Qlik",
|
| 37 |
+
"Python (pandas, numpy) ou R (dplyr, ggplot2) pour les profils plus techniques",
|
| 38 |
+
"Outils de data warehouse : BigQuery, Snowflake, Redshift (en consommation)",
|
| 39 |
+
"Outils de collaboration : Confluence, Notion, Jira",
|
| 40 |
+
"Git en lecture / basique pour récupérer des scripts ou requêtes partagées"
|
| 41 |
+
],
|
| 42 |
+
"competences_soft": [
|
| 43 |
+
"Capacité à comprendre rapidement les besoins métier",
|
| 44 |
+
"Clarté dans la communication écrite et orale",
|
| 45 |
+
"Esprit de synthèse et sens de la pédagogie",
|
| 46 |
+
"Rigueur et attention aux détails",
|
| 47 |
+
"Capacité à challenger les chiffres et à détecter des incohérences"
|
| 48 |
+
],
|
| 49 |
+
"niveau_etude": "Bac+3 à Bac+5 (licence pro, bachelor, Master en statistiques, économie, informatique décisionnelle ou école de commerce avec majeure data/BI).",
|
| 50 |
+
"formations_utiles": [
|
| 51 |
+
"Licence / Master en statistiques, MIAGE, économie quantitative",
|
| 52 |
+
"Master ou mastère spécialisé en Business Intelligence ou data analytics",
|
| 53 |
+
"Bootcamps data analytics (3–6 mois)",
|
| 54 |
+
"Certifications Power BI, Tableau, Looker"
|
| 55 |
+
],
|
| 56 |
+
"experience_requise": "0–2 ans pour un profil junior, 3–5 ans pour un profil confirmé pouvant piloter un périmètre métier."
|
| 57 |
+
},
|
| 58 |
+
{
|
| 59 |
+
"id": "bi_analyst",
|
| 60 |
+
"nom": "Business Intelligence Analyst",
|
| 61 |
+
"categorie": "Noyau data & analytique",
|
| 62 |
+
"description": "Conçoit et administre des solutions décisionnelles (BI) pour fournir aux métiers une vision fiable et partagée des indicateurs clés.",
|
| 63 |
+
"missions_principales": [
|
| 64 |
+
"Recueillir les besoins des métiers en matière de reporting et d’indicateurs",
|
| 65 |
+
"Concevoir et développer des tableaux de bord dynamiques et interactifs",
|
| 66 |
+
"Modéliser les données pour le décisionnel (schémas en étoile, cubes OLAP)",
|
| 67 |
+
"Optimiser les requêtes et sources pour garantir de bonnes performances",
|
| 68 |
+
"Administrer les plateformes BI (droits, rafraîchissements, gouvernance des reports)"
|
| 69 |
+
],
|
| 70 |
+
"competences_techniques": [
|
| 71 |
+
"Maîtrise des concepts BI (dimensions, faits, grain, mesures, KPI)",
|
| 72 |
+
"SQL avancé appliqué au décisionnel",
|
| 73 |
+
"Conception de schémas en étoile et modèles analytiques",
|
| 74 |
+
"Utilisation avancée d’un ou plusieurs outils BI (DAX, MDX, calculs complexes)",
|
| 75 |
+
"Gestion des sources de données, connexions, gateways et rafraîchissement",
|
| 76 |
+
"Notions de performance tuning sur requêtes et modèles BI",
|
| 77 |
+
"Compréhension des principes de gouvernance BI (catalogue de rapports, validation, cycles de vie)"
|
| 78 |
+
],
|
| 79 |
+
"outils_technologies": [
|
| 80 |
+
"Power BI (DAX, Power Query), Tableau, Looker, Qlik",
|
| 81 |
+
"SQL Server, Oracle, PostgreSQL",
|
| 82 |
+
"Outils ETL/ELT simples (Power Query, Fivetran, Stitch, Talend dans certains environnements)",
|
| 83 |
+
"Outils de gestion de versions de rapports / assets BI (Git, workspaces managés)"
|
| 84 |
+
],
|
| 85 |
+
"competences_soft": [
|
| 86 |
+
"Excellente compréhension des besoins métier et de la chaîne de décision",
|
| 87 |
+
"Capacité à prioriser les demandes de reporting",
|
| 88 |
+
"Sens de la pédagogie pour former les utilisateurs aux outils BI",
|
| 89 |
+
"Communication claire avec les équipes IT et métiers"
|
| 90 |
+
],
|
| 91 |
+
"niveau_etude": "Bac+3 à Bac+5 en informatique décisionnelle, statistiques, école d’ingénieur/commerce avec spécialisation BI.",
|
| 92 |
+
"formations_utiles": [
|
| 93 |
+
"Master ou mastère spécialisé en Business Intelligence / décisionnel",
|
| 94 |
+
"Formations éditeur (Power BI, Tableau, Qlik)",
|
| 95 |
+
"Certifications officielles des outils BI"
|
| 96 |
+
],
|
| 97 |
+
"experience_requise": "1–3 ans dans le décisionnel ou la data pour être opérationnel en autonomie."
|
| 98 |
+
},
|
| 99 |
+
{
|
| 100 |
+
"id": "data_miner",
|
| 101 |
+
"nom": "Data Miner",
|
| 102 |
+
"categorie": "Noyau data & analytique",
|
| 103 |
+
"description": "Utilise des techniques de fouille de données pour découvrir des patterns, segments et relations cachées dans de grands volumes de données.",
|
| 104 |
+
"missions_principales": [
|
| 105 |
+
"Préparer et explorer de grands volumes de données",
|
| 106 |
+
"Mettre en œuvre des méthodes de segmentation, scoring, clustering",
|
| 107 |
+
"Identifier des patterns et signaux faibles pertinents pour le métier",
|
| 108 |
+
"Tester et valider des hypothèses statistiques",
|
| 109 |
+
"Documenter les résultats et proposer des recommandations opérationnelles"
|
| 110 |
+
],
|
| 111 |
+
"competences_techniques": [
|
| 112 |
+
"Statistiques descriptives et inférentielles",
|
| 113 |
+
"Techniques de clustering (K-means, DBSCAN, hiérarchique, etc.)",
|
| 114 |
+
"Réduction de dimension (PCA, t-SNE, UMAP)",
|
| 115 |
+
"Notions de machine learning supervisé pour scoring prédictif",
|
| 116 |
+
"Manipulation de données avec SQL et Python/R",
|
| 117 |
+
"Visualisation avancée (cartes de chaleur, matrices de corrélation, etc.)"
|
| 118 |
+
],
|
| 119 |
+
"outils_technologies": [
|
| 120 |
+
"Python (pandas, scikit-learn, scipy)",
|
| 121 |
+
"R (tidyverse, caret, factoextra)",
|
| 122 |
+
"Outils de data mining type RapidMiner, KNIME, Orange dans certains contextes",
|
| 123 |
+
"SQL pour l’extraction de données",
|
| 124 |
+
"BI/visualisation pour présenter les résultats"
|
| 125 |
+
],
|
| 126 |
+
"competences_soft": [
|
| 127 |
+
"Curiosité analytique et capacité à formuler des hypothèses",
|
| 128 |
+
"Rigueur dans la méthodologie et la validation",
|
| 129 |
+
"Capacité à expliquer des patterns complexes de façon accessible"
|
| 130 |
+
],
|
| 131 |
+
"niveau_etude": "Bac+3 à Bac+5 en statistiques, data science, mathématiques appliquées.",
|
| 132 |
+
"formations_utiles": [
|
| 133 |
+
"Licence/Master en statistiques ou data science",
|
| 134 |
+
"Formations data mining, segmentation, scoring marketing",
|
| 135 |
+
"MOOC spécialisés en data mining et clustering"
|
| 136 |
+
],
|
| 137 |
+
"experience_requise": "1–3 ans d’expérience en analyse de données ou data science."
|
| 138 |
+
},
|
| 139 |
+
{
|
| 140 |
+
"id": "statistician",
|
| 141 |
+
"nom": "Statisticien Data",
|
| 142 |
+
"categorie": "Noyau data & analytique",
|
| 143 |
+
"description": "Conçoit des plans d’échantillonnage et des modèles statistiques pour analyser et interpréter les données de manière rigoureuse.",
|
| 144 |
+
"missions_principales": [
|
| 145 |
+
"Concevoir des études statistiques et plans de sondage",
|
| 146 |
+
"Choisir et appliquer les méthodes statistiques appropriées",
|
| 147 |
+
"Valider la qualité des données et la robustesse des résultats",
|
| 148 |
+
"Produire des rapports et avis statistiques pour éclairer les décisions",
|
| 149 |
+
"Collaborer avec des data scientists et métiers sur la méthodologie"
|
| 150 |
+
],
|
| 151 |
+
"competences_techniques": [
|
| 152 |
+
"Statistiques descriptives, inférentielles, modèles linéaires et généralisés",
|
| 153 |
+
"Tests d’hypothèses, intervalles de confiance, ANOVA",
|
| 154 |
+
"Modèles de régression (linéaire, logistique, Poisson, etc.)",
|
| 155 |
+
"Méthodes bayésiennes de base",
|
| 156 |
+
"Logiciels statistiques (R, SAS, éventuellement Stata/SPSS)",
|
| 157 |
+
"Programmation en R ou Python pour l’automatisation d’analyses",
|
| 158 |
+
"Gestion de données d’enquête et pondérations"
|
| 159 |
+
],
|
| 160 |
+
"outils_technologies": [
|
| 161 |
+
"R (tidyverse, lme4, brms, etc.)",
|
| 162 |
+
"SAS, SPSS, Stata selon les secteurs",
|
| 163 |
+
"Python (pandas, statsmodels, scipy)",
|
| 164 |
+
"SQL pour l’accès aux données",
|
| 165 |
+
"LaTeX ou RMarkdown pour la production de rapports"
|
| 166 |
+
],
|
| 167 |
+
"competences_soft": [
|
| 168 |
+
"Rigueur scientifique et sens du détail",
|
| 169 |
+
"Capacité à challenger la qualité des données et les hypothèses",
|
| 170 |
+
"Bonne communication avec des non-statisticiens"
|
| 171 |
+
],
|
| 172 |
+
"niveau_etude": "Bac+5 minimum (Master en statistiques, mathématiques appliquées) ; Doctorat apprécié dans certains domaines (santé, recherche, R&D).",
|
| 173 |
+
"formations_utiles": [
|
| 174 |
+
"Master en statistiques ou biostatistiques",
|
| 175 |
+
"Doctorat en statistiques / mathématiques appliquées",
|
| 176 |
+
"Formations spécialisées selon le domaine (santé, économie, industrie)"
|
| 177 |
+
],
|
| 178 |
+
"experience_requise": "0–2 ans pour junior, 3–5 ans pour confirmé."
|
| 179 |
+
},
|
| 180 |
+
{
|
| 181 |
+
"id": "data_scientist",
|
| 182 |
+
"nom": "Data Scientist",
|
| 183 |
+
"categorie": "Noyau data & analytique",
|
| 184 |
+
"description": "Conçoit des modèles prédictifs, explore les données et construit des solutions ML/IA pour répondre à des problématiques métier complexes.",
|
| 185 |
+
"missions_principales": [
|
| 186 |
+
"Réaliser l'exploration et la préparation de données sur différentes sources et formats",
|
| 187 |
+
"Concevoir, entraîner, évaluer et comparer des modèles de machine learning et deep learning",
|
| 188 |
+
"Traduire des besoins métier en problématiques data et en indicateurs mesurables",
|
| 189 |
+
"Industrialiser des prototypes en scripts ou jobs reproductibles",
|
| 190 |
+
"Collaborer avec les équipes data engineering, produit et métier",
|
| 191 |
+
"Communiquer les résultats via visualisations, rapports et data storytelling"
|
| 192 |
+
],
|
| 193 |
+
"competences_techniques": [
|
| 194 |
+
"Python avancé (pandas, numpy, scipy, scikit-learn, statsmodels)",
|
| 195 |
+
"Programmation orientée objet, bonnes pratiques (tests unitaires, packaging, logging, CI)",
|
| 196 |
+
"Statistiques avancées (tests d’hypothèses, échantillonnage, bootstrap, méthodes bayésiennes de base)",
|
| 197 |
+
"Machine learning supervisé et non supervisé (régression, arbres, gradient boosting, SVM, clustering, réduction de dimension)",
|
| 198 |
+
"Deep learning (CNN, RNN, transformers) pour texte, image et séries temporelles",
|
| 199 |
+
"Traitement de données à grande échelle avec Spark / PySpark ou Dask",
|
| 200 |
+
"Feature engineering, sélection de variables, encoding, normalisation, gestion des valeurs manquantes",
|
| 201 |
+
"Évaluation de modèles (cross-validation, métriques de classification et régression, AUC, F1, courbes PR, calibration)",
|
| 202 |
+
"Concepts de MLOps de base (suivi d’expériences, versioning de modèles et de données, reproductibilité)",
|
| 203 |
+
"Cloud data & AI (AWS, GCP ou Azure) pour entraînement et déploiement",
|
| 204 |
+
"Bon niveau SQL (jointures complexes, CTE, window functions, optimisation)",
|
| 205 |
+
"Connaissances de base en LLMs et GenAI (APIs, RAG simple, prompt engineering de base)"
|
| 206 |
+
],
|
| 207 |
+
"outils_technologies": [
|
| 208 |
+
"Python, SQL, éventuellement R",
|
| 209 |
+
"scikit-learn, XGBoost, LightGBM, CatBoost",
|
| 210 |
+
"TensorFlow, Keras, PyTorch, Hugging Face Transformers",
|
| 211 |
+
"Apache Spark / PySpark, Dask",
|
| 212 |
+
"pandas, polars",
|
| 213 |
+
"Matplotlib, Seaborn, Plotly, Power BI, Tableau",
|
| 214 |
+
"MLflow, Weights & Biases, DVC",
|
| 215 |
+
"JupyterLab, VS Code, Databricks, Vertex AI Workbench, SageMaker Studio",
|
| 216 |
+
"PostgreSQL, MySQL, BigQuery, Snowflake, MongoDB",
|
| 217 |
+
"Git, GitHub/GitLab, GitHub Actions / GitLab CI"
|
| 218 |
+
],
|
| 219 |
+
"competences_soft": [
|
| 220 |
+
"Capacité à vulgariser des résultats techniques à des publics non techniques",
|
| 221 |
+
"Orientation produit et impact business",
|
| 222 |
+
"Esprit critique et rigueur scientifique",
|
| 223 |
+
"Curiosité et veille technologique continue",
|
| 224 |
+
"Travail en équipe, pair programming ponctuel, feedback constructif"
|
| 225 |
+
],
|
| 226 |
+
"niveau_etude": "Bac+5 (Master data science, statistiques, mathématiques appliquées ou école d’ingénieur) ; Doctorat apprécié pour des postes orientés R&D.",
|
| 227 |
+
"formations_utiles": [
|
| 228 |
+
"Master Data Science / IA",
|
| 229 |
+
"École d’ingénieur avec majeure data/IA",
|
| 230 |
+
"Certifications cloud orientées data/ML (AWS ML Specialty, GCP Professional ML Engineer, Azure DP-100)",
|
| 231 |
+
"MOOC/bootcamps avancés en machine learning, deep learning, NLP et MLOps"
|
| 232 |
+
],
|
| 233 |
+
"experience_requise": "0–2 ans pour junior, 3–5 ans pour confirmé, 6+ ans pour senior / lead."
|
| 234 |
+
},
|
| 235 |
+
{
|
| 236 |
+
"metiers": [
|
| 237 |
+
{
|
| 238 |
+
"id": "data_engineer",
|
| 239 |
+
"nom": "Data Engineer",
|
| 240 |
+
"categorie": "Ingénierie data & IA",
|
| 241 |
+
"description": "Construit et maintient les pipelines et plateformes de données modernes (batch et streaming) pour alimenter l’analytics, le reporting et les systèmes d’IA.",
|
| 242 |
+
"missions_principales": [
|
| 243 |
+
"Concevoir et implémenter des architectures de données (data lake, data warehouse, lakehouse)",
|
| 244 |
+
"Mettre en place des pipelines ELT/ETL robustes, monitorés et testés",
|
| 245 |
+
"Gérer l’ingestion de données en batch et temps réel (streaming)",
|
| 246 |
+
"Optimiser les performances, la disponibilité et les coûts des plateformes data, notamment dans le cloud",
|
| 247 |
+
"Collaborer avec data scientists, analysts et équipes produit pour exposer des données consommables",
|
| 248 |
+
"Mettre en œuvre des pratiques de qualité, sécurité et gouvernance des données"
|
| 249 |
+
],
|
| 250 |
+
"competences_techniques": [
|
| 251 |
+
"SQL avancé (requêtes complexes, tuning, indexation, partitionnement)",
|
| 252 |
+
"Python pour data engineering (scripts, packages, tests, typage, logging)",
|
| 253 |
+
"Modern data stack : dbt (data modeling & transformations), orchestration (Airflow, Dagster, Prefect)",
|
| 254 |
+
"Streaming & messaging : Apache Kafka, Confluent, AWS Kinesis, GCP Pub/Sub",
|
| 255 |
+
"Traitement distribué : Apache Spark (batch & streaming), éventuellement Flink ou Beam",
|
| 256 |
+
"Cloud data warehouses : Snowflake, BigQuery, Redshift, Synapse",
|
| 257 |
+
"Stockage data lake : S3, GCS, ADLS ; formats Parquet, ORC, Delta Lake, Apache Iceberg",
|
| 258 |
+
"CI/CD & DevOps : Git, pipelines CI, conteneurisation (Docker), infrastructure as code (Terraform, CloudFormation, Pulumi)",
|
| 259 |
+
"Concepts d’architecture data (lambda, kappa, lakehouse, microservices data)",
|
| 260 |
+
"Notions de sécurité : IAM, chiffrement au repos/en transit, gestion des secrets, data masking",
|
| 261 |
+
"Tests de données : tests unitaires, de contrats et de qualité (schémas, contraintes, anomalies)"
|
| 262 |
+
],
|
| 263 |
+
"outils_technologies": [
|
| 264 |
+
"Orchestration : Apache Airflow, Dagster, Prefect",
|
| 265 |
+
"Transformation : dbt (core ou cloud)",
|
| 266 |
+
"Streaming : Kafka, Confluent Platform, AWS Kinesis, GCP Pub/Sub",
|
| 267 |
+
"Traitement : Spark (Databricks, EMR, DataProc), Flink, Beam",
|
| 268 |
+
"Cloud : AWS (Glue, Redshift, S3), GCP (Dataflow, BigQuery, GCS), Azure (Data Factory, Synapse, ADLS)",
|
| 269 |
+
"Data warehouse : Snowflake, BigQuery, Redshift, Synapse",
|
| 270 |
+
"Monitoring : Prometheus, Grafana, services de monitoring cloud natif",
|
| 271 |
+
"Infra as code : Terraform, CloudFormation, Pulumi",
|
| 272 |
+
"Conteneurs : Docker, Kubernetes (EKS, GKE, AKS)",
|
| 273 |
+
"Qualité & catalogue : Great Expectations, Soda Core, OpenMetadata, Amundsen"
|
| 274 |
+
],
|
| 275 |
+
"competences_soft": [
|
| 276 |
+
"Orientation fiabilité et résilience (SRE mindset appliqué aux données)",
|
| 277 |
+
"Capacité à collaborer avec data scientists, analysts, product et infra",
|
| 278 |
+
"Documentation claire des architectures et pipelines",
|
| 279 |
+
"Analyse de risques (SLA/SLO, volumétrie, coûts cloud, dette technique)",
|
| 280 |
+
"Autonomie et capacité à prioriser les chantiers structurants"
|
| 281 |
+
],
|
| 282 |
+
"niveau_etude": "Bac+3 à Bac+5 (licence pro, Master, école d’ingénieur en informatique, systèmes distribués ou data).",
|
| 283 |
+
"formations_utiles": [
|
| 284 |
+
"Master Big Data / Systèmes distribués / Cloud",
|
| 285 |
+
"Bootcamps ou formations intensives en data engineering et modern data stack",
|
| 286 |
+
"Certifications cloud data (AWS Data Analytics, GCP Professional Data Engineer, Azure DP-203)",
|
| 287 |
+
"Formations spécialisées Kafka, Spark, dbt, Airflow/Dagster"
|
| 288 |
+
],
|
| 289 |
+
"experience_requise": "1–2 ans pour profils juniors, 3–5 ans pour confirmé, 5+ ans pour data architect."
|
| 290 |
+
},
|
| 291 |
+
{
|
| 292 |
+
"id": "analytics_engineer",
|
| 293 |
+
"nom": "Analytics Engineer",
|
| 294 |
+
"categorie": "Ingénierie data & IA",
|
| 295 |
+
"description": "Fait le pont entre data engineering et data analytics : modèle les données pour les rendre directement exploitables par les analystes et les métiers.",
|
| 296 |
+
"missions_principales": [
|
| 297 |
+
"Modéliser les données analytiques (modèles dimensionnels, marts orientés métier)",
|
| 298 |
+
"Écrire et maintenir les transformations SQL (ELT) dans le data warehouse",
|
| 299 |
+
"Documenter les modèles de données et les définitions de KPI",
|
| 300 |
+
"Collaborer avec les data analysts pour répondre rapidement aux besoins métier",
|
| 301 |
+
"Assurer la qualité, la cohérence et la performance des datasets analytiques"
|
| 302 |
+
],
|
| 303 |
+
"competences_techniques": [
|
| 304 |
+
"SQL avancé (window functions, CTE, optimisation)",
|
| 305 |
+
"Modélisation de données BI (étoile, snowflake, data vault simplifié)",
|
| 306 |
+
"Transformation ELT dans un data warehouse cloud",
|
| 307 |
+
"Utilisation avancée de dbt (models, tests, documentation, lineage)",
|
| 308 |
+
"Notions de performance des requêtes et des schémas dans DW cloud",
|
| 309 |
+
"Compréhension des besoins métier et des métriques business",
|
| 310 |
+
"Notions de Git et CI pour les projets analytics"
|
| 311 |
+
],
|
| 312 |
+
"outils_technologies": [
|
| 313 |
+
"dbt (core/cloud)",
|
| 314 |
+
"Data warehouses : Snowflake, BigQuery, Redshift, Synapse",
|
| 315 |
+
"SQL (PostgreSQL, BigQuery, Snowflake)",
|
| 316 |
+
"Orchestration : Airflow, Dagster, Prefect (en collaboration avec data engineers)",
|
| 317 |
+
"BI : Power BI, Looker, Tableau (en consommation et préparation de modèles)",
|
| 318 |
+
"Git, GitHub/GitLab pour versionner le code analytics"
|
| 319 |
+
],
|
| 320 |
+
"competences_soft": [
|
| 321 |
+
"Forte sensibilité business",
|
| 322 |
+
"Communication efficace avec les métiers et les analystes",
|
| 323 |
+
"Rigueur dans la documentation et la définition de KPI",
|
| 324 |
+
"Capacité à prioriser les demandes analytics"
|
| 325 |
+
],
|
| 326 |
+
"niveau_etude": "Bac+3 à Bac+5 (informatique décisionnelle, statistiques, école d’ingénieur ou commerce avec spécialisation data).",
|
| 327 |
+
"formations_utiles": [
|
| 328 |
+
"Formations modern data stack (dbt, Snowflake/BigQuery, Looker/Power BI)",
|
| 329 |
+
"Bootcamps data analytics / analytics engineering",
|
| 330 |
+
"Certifications sur data warehouses cloud"
|
| 331 |
+
],
|
| 332 |
+
"experience_requise": "1–3 ans en data analysis/BI ou data engineering."
|
| 333 |
+
},
|
| 334 |
+
{
|
| 335 |
+
"id": "dataops_engineer",
|
| 336 |
+
"nom": "DataOps Engineer",
|
| 337 |
+
"categorie": "Ingénierie data & IA",
|
| 338 |
+
"description": "Industrialise et automatise les processus data (du sourcing à la consommation) en appliquant les principes DevOps au monde de la donnée.",
|
| 339 |
+
"missions_principales": [
|
| 340 |
+
"Automatiser le déploiement et la surveillance des pipelines de données",
|
| 341 |
+
"Mettre en place des tests et contrôles qualité sur les données",
|
| 342 |
+
"Gérer les environnements (dev, test, prod) pour les workflows data",
|
| 343 |
+
"Superviser les SLA/SLO des flux et agir en cas d’incidents",
|
| 344 |
+
"Collaborer avec data engineers, analysts et SRE pour fiabiliser la plateforme"
|
| 345 |
+
],
|
| 346 |
+
"competences_techniques": [
|
| 347 |
+
"Bon niveau en Python et/ou bash pour l’automatisation",
|
| 348 |
+
"Orchestration de workflows (Airflow, Dagster, Prefect)",
|
| 349 |
+
"CI/CD (GitHub Actions, GitLab CI, Jenkins)",
|
| 350 |
+
"Monitoring & alerting (Prometheus, Grafana, outils cloud natifs)",
|
| 351 |
+
"Concepts DataOps (tests de données, data contracts, observabilité des données)",
|
| 352 |
+
"Connaissance des architectures data et des principaux outils de la stack data",
|
| 353 |
+
"Notions de conteneurisation et Kubernetes"
|
| 354 |
+
],
|
| 355 |
+
"outils_technologies": [
|
| 356 |
+
"Airflow, Dagster, Prefect",
|
| 357 |
+
"Git, GitHub/GitLab, Jenkins",
|
| 358 |
+
"Prometheus, Grafana, Loki, outils de logs",
|
| 359 |
+
"Great Expectations, Soda pour la qualité des données",
|
| 360 |
+
"Docker, Kubernetes",
|
| 361 |
+
"Outils de ticketing (Jira) et de gestion d’incidents"
|
| 362 |
+
],
|
| 363 |
+
"competences_soft": [
|
| 364 |
+
"Culture fiabilité / SRE appliquée aux données",
|
| 365 |
+
"Capacité à collaborer avec plusieurs profils (ingénieurs, ops, métier)",
|
| 366 |
+
"Rigueur, sens du détail, gestion de l’urgence en cas d’incident"
|
| 367 |
+
],
|
| 368 |
+
"niveau_etude": "Bac+3 à Bac+5 en informatique, systèmes, data engineering.",
|
| 369 |
+
"formations_utiles": [
|
| 370 |
+
"Formations DevOps/Cloud complétées par un volet data",
|
| 371 |
+
"Certifications cloud (AWS, GCP, Azure)",
|
| 372 |
+
"Formations DataOps, Data Observability"
|
| 373 |
+
],
|
| 374 |
+
"experience_requise": "2–4 ans en data engineering, DevOps ou SRE."
|
| 375 |
+
},
|
| 376 |
+
{
|
| 377 |
+
"id": "mlops_engineer",
|
| 378 |
+
"nom": "MLOps Engineer",
|
| 379 |
+
"categorie": "Ingénierie data & IA",
|
| 380 |
+
"description": "Automatise et fiabilise le cycle de vie des modèles de machine learning (training, déploiement, monitoring, retraining) en production.",
|
| 381 |
+
"missions_principales": [
|
| 382 |
+
"Concevoir et maintenir des pipelines de training, validation et déploiement des modèles",
|
| 383 |
+
"Intégrer le tracking d’expériences, de datasets et de modèles dans les workflows",
|
| 384 |
+
"Mettre en place le monitoring des modèles (performance, dérive, incidents)",
|
| 385 |
+
"Automatiser le retraining et la mise à jour des modèles",
|
| 386 |
+
"Collaborer avec data scientists, ML engineers et équipes infra/DevOps"
|
| 387 |
+
],
|
| 388 |
+
"competences_techniques": [
|
| 389 |
+
"Solide base DevOps (Linux, réseaux, CI/CD, conteneurs, Kubernetes)",
|
| 390 |
+
"Connaissance pratique des frameworks ML (scikit-learn, TensorFlow, PyTorch)",
|
| 391 |
+
"MLOps : experiment tracking, model registry, feature store, pipelines ML",
|
| 392 |
+
"Outils de déploiement ML (batch, API temps réel, streaming)",
|
| 393 |
+
"Monitoring de modèles : dérive, performance, fairness, logs",
|
| 394 |
+
"Cloud AI : SageMaker, Vertex AI, Azure ML, Databricks ML",
|
| 395 |
+
"Notions de sécurité et conformité spécifique à l’IA (audit, traçabilité)"
|
| 396 |
+
],
|
| 397 |
+
"outils_technologies": [
|
| 398 |
+
"MLflow, Kubeflow, ZenML, Metaflow",
|
| 399 |
+
"Weights & Biases, Neptune.ai",
|
| 400 |
+
"CI/CD : GitHub Actions, GitLab CI, Argo CD",
|
| 401 |
+
"Serving : FastAPI, TorchServe, TF Serving, Triton, BentoML",
|
| 402 |
+
"Infrastructure : Docker, Kubernetes, Helm",
|
| 403 |
+
"Monitoring : Prometheus, Grafana, outils de model monitoring (Evidently AI, WhyLabs)",
|
| 404 |
+
"Cloud AI : AWS SageMaker, GCP Vertex AI, Azure ML"
|
| 405 |
+
],
|
| 406 |
+
"competences_soft": [
|
| 407 |
+
"Culture de la fiabilité et de l’automatisation",
|
| 408 |
+
"Communication avec data scientists et DevOps",
|
| 409 |
+
"Capacité à standardiser les pratiques dans l’équipe",
|
| 410 |
+
"Veille technologique sur l’écosystème MLOps/LLMOps"
|
| 411 |
+
],
|
| 412 |
+
"niveau_etude": "Bac+3 à Bac+5 en informatique, data, ou équivalent, avec forte appétence pour l’IA et le DevOps.",
|
| 413 |
+
"formations_utiles": [
|
| 414 |
+
"Formations MLOps spécialisées",
|
| 415 |
+
"Certifications cloud ML (AWS, GCP, Azure)",
|
| 416 |
+
"Formations avancées en Kubernetes et CI/CD"
|
| 417 |
+
],
|
| 418 |
+
"experience_requise": "2–5 ans en ML engineering, DevOps ou data engineering."
|
| 419 |
+
},
|
| 420 |
+
{
|
| 421 |
+
"id": "ml_engineer",
|
| 422 |
+
"nom": "Machine Learning Engineer",
|
| 423 |
+
"categorie": "Ingénierie data & IA",
|
| 424 |
+
"description": "Met en production des modèles de ML/IA, conçoit des APIs de prédiction et gère le cycle de vie des modèles (training, déploiement, monitoring, retraining).",
|
| 425 |
+
"missions_principales": [
|
| 426 |
+
"Industrialiser des prototypes de data science en services robustes et scalables",
|
| 427 |
+
"Concevoir des APIs de prédiction (batch, temps réel, streaming) pour les produits",
|
| 428 |
+
"Mettre en place des pipelines de training, validation, déploiement et monitoring de modèles",
|
| 429 |
+
"Optimiser les performances (latence, coût, consommation GPU/CPU) et la robustesse des systèmes ML",
|
| 430 |
+
"Collaborer avec les équipes produit, data science, DevOps/MLOps et sécurité"
|
| 431 |
+
],
|
| 432 |
+
"competences_techniques": [
|
| 433 |
+
"Python avancé et bonnes pratiques d’ingénierie logicielle (tests, patterns, CI/CD, observabilité)",
|
| 434 |
+
"Maîtrise de frameworks ML et deep learning (scikit-learn, TensorFlow, PyTorch)",
|
| 435 |
+
"Connaissance des LLMs et transformers (fine-tuning, prompt engineering, RAG)",
|
| 436 |
+
"MLOps : conception de pipelines de training et déploiement, gestion des versions de modèles et de données",
|
| 437 |
+
"Déploiement de modèles : REST/gRPC, batch inference, streaming inference",
|
| 438 |
+
"Optimisation : quantization, pruning, distillation, ONNX, TensorRT, optimisation GPU",
|
| 439 |
+
"Monitoring de modèles (dérive de données, dérive de labels, performance en production, fairness)",
|
| 440 |
+
"Cloud AI : SageMaker, Vertex AI, Azure ML, Databricks ML",
|
| 441 |
+
"Bases solides en statistiques, ML classique et deep learning",
|
| 442 |
+
"Notions de sécurité spécifiques à l’IA (attaques adversariales, prompt injection pour LLMs)"
|
| 443 |
+
],
|
| 444 |
+
"outils_technologies": [
|
| 445 |
+
"scikit-learn, XGBoost, LightGBM",
|
| 446 |
+
"TensorFlow, Keras, PyTorch",
|
| 447 |
+
"Hugging Face (Transformers, PEFT), LangChain, LlamaIndex, APIs OpenAI/Anthropic",
|
| 448 |
+
"MLflow, Kubeflow, ZenML, DVC, Weights & Biases",
|
| 449 |
+
"FastAPI, Flask, TorchServe, TensorFlow Serving, Triton Inference Server, BentoML",
|
| 450 |
+
"GitHub Actions, GitLab CI, Argo CD, Tekton",
|
| 451 |
+
"Docker, Kubernetes, Helm, GPU sur cloud",
|
| 452 |
+
"Prometheus, Grafana, outils de model monitoring spécialisés"
|
| 453 |
+
],
|
| 454 |
+
"competences_soft": [
|
| 455 |
+
"Capacité à arbitrer entre performance modèle et contraintes produit (latence, coûts, maintenance)",
|
| 456 |
+
"Travail rapproché avec les équipes infra, data engineering et produit",
|
| 457 |
+
"Sens de la fiabilité, de l’observabilité et de l’automatisation",
|
| 458 |
+
"Capacité à documenter des systèmes complexes (diagrammes d’architecture, RFC techniques, READMEs)"
|
| 459 |
+
],
|
| 460 |
+
"niveau_etude": "Bac+5 (Master/école d’ingénieur en IA, informatique, math appliquées) ; doctorat utile pour postes de R&D avancés.",
|
| 461 |
+
"formations_utiles": [
|
| 462 |
+
"Master IA / ML Engineering",
|
| 463 |
+
"Formations spécialisées MLOps et architectures cloud-native",
|
| 464 |
+
"Certifications cloud ML (AWS, GCP, Azure)",
|
| 465 |
+
"Formations avancées en LLMs, GenAI, optimisation de modèles et systèmes de recommandation"
|
| 466 |
+
],
|
| 467 |
+
"experience_requise": "1–3 ans pour ML Engineer junior, 3–6 ans pour confirmé, 6+ ans pour Staff / Principal ML Engineer."
|
| 468 |
+
},
|
| 469 |
+
{
|
| 470 |
+
"id": "deep_learning_engineer",
|
| 471 |
+
"nom": "Deep Learning Engineer",
|
| 472 |
+
"categorie": "Ingénierie data & IA",
|
| 473 |
+
"description": "Spécialiste des réseaux de neurones profonds, conçoit des architectures deep learning pour la vision, le langage, le son ou les séries temporelles.",
|
| 474 |
+
"missions_principales": [
|
| 475 |
+
"Concevoir et entraîner des architectures de réseaux de neurones adaptées aux cas d’usage",
|
| 476 |
+
"Optimiser les modèles pour la précision, la robustesse et la performance",
|
| 477 |
+
"Exploiter le GPU et les accélérateurs matériels pour accélérer l’entraînement et l’inférence",
|
| 478 |
+
"Collaborer avec les équipes produit et recherche sur les approches d’IA avancées",
|
| 479 |
+
"Documenter et partager les résultats d’expérimentation"
|
| 480 |
+
],
|
| 481 |
+
"competences_techniques": [
|
| 482 |
+
"Python avancé, POO, bonnes pratiques de code scientifique",
|
| 483 |
+
"Maîtrise de PyTorch et/ou TensorFlow pour le deep learning",
|
| 484 |
+
"Connaissance des architectures CNN, RNN, LSTM, transformers, diffusion models",
|
| 485 |
+
"Optimisation de l’entraînement (schedule de learning rate, régularisation, augmentation de données)",
|
| 486 |
+
"Utilisation du GPU (CUDA, cuDNN, profiling) et éventuellement TPU",
|
| 487 |
+
"Gestion de larges datasets (chargement, préprocessing, data loaders optimisés)",
|
| 488 |
+
"Notions de recherche (lecture et implémentation d’articles récents)",
|
| 489 |
+
"Suivi d’expériences et reproduction de résultats"
|
| 490 |
+
],
|
| 491 |
+
"outils_technologies": [
|
| 492 |
+
"PyTorch, TensorFlow, Keras",
|
| 493 |
+
"Hugging Face Transformers, timm, diffusers",
|
| 494 |
+
"CUDA, cuDNN, PyTorch Lightning, Accelerate",
|
| 495 |
+
"Weights & Biases, MLflow, Neptune.ai",
|
| 496 |
+
"Jupyter, VS Code, environnement GPU (on-prem ou cloud)",
|
| 497 |
+
"Outils de profiling GPU/CPU"
|
| 498 |
+
],
|
| 499 |
+
"competences_soft": [
|
| 500 |
+
"Curiosité scientifique, goût pour l’expérimentation",
|
| 501 |
+
"Capacité à lire et implémenter des papiers de recherche",
|
| 502 |
+
"Rigueur dans l’évaluation et la réplication de résultats"
|
| 503 |
+
],
|
| 504 |
+
"niveau_etude": "Bac+5 à Bac+8 (Master IA, école d’ingénieur, doctorat en IA/vision/NLP).",
|
| 505 |
+
"formations_utiles": [
|
| 506 |
+
"Master spécialisé en deep learning",
|
| 507 |
+
"Doctorat en IA / vision / NLP",
|
| 508 |
+
"Formations NVIDIA, fast.ai, MOOCs DL avancés"
|
| 509 |
+
],
|
| 510 |
+
"experience_requise": "1–3 ans en DL appliqué, plus pour les rôles senior/R&D."
|
| 511 |
+
},
|
| 512 |
+
{
|
| 513 |
+
"id": "ai_engineer",
|
| 514 |
+
"nom": "AI Engineer",
|
| 515 |
+
"categorie": "Ingénierie data & IA",
|
| 516 |
+
"description": "Construit des systèmes d’IA end-to-end, combinant modèles, règles, APIs, LLMs et intégrations produit pour répondre à des cas d’usage métiers.",
|
| 517 |
+
"missions_principales": [
|
| 518 |
+
"Assembler des composants d’IA (ML classique, deep learning, LLMs, règles, APIs externes) en solutions complètes",
|
| 519 |
+
"Intégrer les systèmes d’IA dans les produits existants (backend, frontend, workflows métier)",
|
| 520 |
+
"Optimiser expérience utilisateur, latence et coûts d’inférence",
|
| 521 |
+
"Assurer la supervision, les logs et la gestion d’erreurs des systèmes IA",
|
| 522 |
+
"Collaborer avec les équipes produit, design et métier pour définir les fonctionnalités IA"
|
| 523 |
+
],
|
| 524 |
+
"competences_techniques": [
|
| 525 |
+
"Bon niveau en Python et un langage backend (TypeScript/Node, Java, Go selon le contexte)",
|
| 526 |
+
"Connaissance de plusieurs familles de modèles (ML, DL, LLMs, systèmes de recommandation)",
|
| 527 |
+
"Intégration d’APIs d’IA (OpenAI, Anthropic, Vertex AI, Hugging Face Inference)",
|
| 528 |
+
"Patterns d’applications LLM (RAG, agents, outils, memory, évaluation)",
|
| 529 |
+
"Déploiement de services IA (APIs, serverless functions, microservices)",
|
| 530 |
+
"Notions de sécurité applicative (auth, rate limiting, gestion de secrets)"
|
| 531 |
+
],
|
| 532 |
+
"outils_technologies": [
|
| 533 |
+
"Python, TypeScript/Node.js",
|
| 534 |
+
"Frameworks web : FastAPI, Flask, Express",
|
| 535 |
+
"LLM frameworks : LangChain, LlamaIndex, semantic-kernel",
|
| 536 |
+
"APIs LLM : OpenAI, Anthropic, Gemini, LLaMA via providers",
|
| 537 |
+
"Vector DB : Pinecone, Weaviate, Qdrant, Redis, PGVector",
|
| 538 |
+
"Cloud : AWS, GCP, Azure (APIs managées d’IA, serverless)",
|
| 539 |
+
"Observabilité : OpenTelemetry, Prometheus, Grafana"
|
| 540 |
+
],
|
| 541 |
+
"competences_soft": [
|
| 542 |
+
"Orientation produit et UX",
|
| 543 |
+
"Capacité à prototyper rapidement et itérer",
|
| 544 |
+
"Communication avec designers, PM et métiers"
|
| 545 |
+
],
|
| 546 |
+
"niveau_etude": "Bac+3 à Bac+5 (informatique/IA), avec forte appétence pour le développement logiciel.",
|
| 547 |
+
"formations_utiles": [
|
| 548 |
+
"Formations en IA appliquée et développement d’applications LLM",
|
| 549 |
+
"Bootcamps IA fullstack",
|
| 550 |
+
"Certifications LLM/GenAI proposées par les hyperscalers"
|
| 551 |
+
],
|
| 552 |
+
"experience_requise": "2–5 ans en dev logiciel et/ou ML engineering."
|
| 553 |
+
},
|
| 554 |
+
{
|
| 555 |
+
"id": "data_platform_engineer",
|
| 556 |
+
"nom": "Data Platform Engineer",
|
| 557 |
+
"categorie": "Ingénierie data & IA",
|
| 558 |
+
"description": "Conçoit, construit et maintient la plateforme data/IA globale (outils, services, standards) utilisée par les équipes data et produit.",
|
| 559 |
+
"missions_principales": [
|
| 560 |
+
"Concevoir l’architecture globale de la plateforme data/IA",
|
| 561 |
+
"Packager des services self-service pour les équipes data (environnements, templates, pipelines)",
|
| 562 |
+
"Gérer l’infrastructure (Kubernetes, cloud, sécurité, monitoring)",
|
| 563 |
+
"Standardiser les outils (suite MLOps, DataOps, gouvernance)",
|
| 564 |
+
"Assurer la scalabilité et l’optimisation des coûts de la plateforme"
|
| 565 |
+
],
|
| 566 |
+
"competences_techniques": [
|
| 567 |
+
"Architecture systèmes distribués",
|
| 568 |
+
"Maîtrise des principaux composants de la stack data & MLOps",
|
| 569 |
+
"Cloud computing (AWS/GCP/Azure) orienté data et IA",
|
| 570 |
+
"Kubernetes, networking, sécurité, observabilité",
|
| 571 |
+
"Infra as Code (Terraform, Pulumi) et GitOps"
|
| 572 |
+
],
|
| 573 |
+
"outils_technologies": [
|
| 574 |
+
"Kubernetes, Helm, Istio/Linkerd (selon contexte)",
|
| 575 |
+
"Terraform, Pulumi, ArgoCD",
|
| 576 |
+
"Spark, Kafka, data warehouses cloud",
|
| 577 |
+
"Outils MLOps/DataOps (MLflow, Airflow, dbt, catalogues, quality tools)",
|
| 578 |
+
"Monitoring : Prometheus, Grafana, Loki"
|
| 579 |
+
],
|
| 580 |
+
"competences_soft": [
|
| 581 |
+
"Leadership technique transversal",
|
| 582 |
+
"Capacité à définir des standards et conventions",
|
| 583 |
+
"Communication avec de multiples équipes",
|
| 584 |
+
"Vision long terme sur l’architecture"
|
| 585 |
+
],
|
| 586 |
+
"niveau_etude": "Bac+5 (école d’ingénieur / Master informatique, systèmes distribués).",
|
| 587 |
+
"formations_utiles": [
|
| 588 |
+
"Formations avancées en cloud & Kubernetes",
|
| 589 |
+
"Certifications architecte cloud (AWS/GCP/Azure)",
|
| 590 |
+
"Formations data platform / data mesh"
|
| 591 |
+
],
|
| 592 |
+
"experience_requise": "5+ ans en data engineering/infra, dont expérience significative en architecture."
|
| 593 |
+
}
|
| 594 |
+
]
|
| 595 |
+
},
|
| 596 |
+
{
|
| 597 |
+
"metiers": [
|
| 598 |
+
{
|
| 599 |
+
"id": "cdo",
|
| 600 |
+
"nom": "Chief Data Officer (CDO)",
|
| 601 |
+
"categorie": "Gouvernance, qualité, protection",
|
| 602 |
+
"description": "Dirige la stratégie data de l’entreprise. Responsable de la gouvernance, de la valorisation et de la conformité des données pour créer de la valeur business.",
|
| 603 |
+
"missions_principales": [
|
| 604 |
+
"Définir et piloter la stratégie data alignée sur les objectifs business",
|
| 605 |
+
"Superviser la gouvernance des données (qualité, métadonnées, catalogue)",
|
| 606 |
+
"Garantir la conformité RGPD, AI Act et autres réglementations",
|
| 607 |
+
"Construire et animer la plateforme data/IA (outils, standards, budget)",
|
| 608 |
+
"Évangeliser la culture data et mesurer l’impact business des initiatives data",
|
| 609 |
+
"Collaborer avec C-level (CEO, CIO, CTO, CMO) sur la transformation data-driven"
|
| 610 |
+
],
|
| 611 |
+
"competences_techniques": [
|
| 612 |
+
"Architecture data et modern data stack (lakehouse, data mesh, data product)",
|
| 613 |
+
"Gouvernance data (qualité, catalogue, lineage, ownership)",
|
| 614 |
+
"Conformité RGPD, AI Act, NIST AI RMF, ISO 42001",
|
| 615 |
+
"Cloud data platforms (AWS, GCP, Azure) et coûts optimisation",
|
| 616 |
+
"Data product management et data mesh",
|
| 617 |
+
"AI/ML gouvernance (bias, fairness, explainability, model cards)",
|
| 618 |
+
"KPI data (ROI data projects, data maturity, adoption metrics)",
|
| 619 |
+
"Outils de data governance et catalogue"
|
| 620 |
+
],
|
| 621 |
+
"outils_technologies": [
|
| 622 |
+
"Data catalogs : Collibra, Alation, DataHub, Amundsen, OpenMetadata",
|
| 623 |
+
"Data governance : Informatica, Talend, Atlan",
|
| 624 |
+
"Data quality : Great Expectations, Soda, Monte Carlo",
|
| 625 |
+
"Cloud data platforms : Snowflake, BigQuery, Databricks",
|
| 626 |
+
"Observabilité data : Monte Carlo, Bigeye",
|
| 627 |
+
"Outils de conformité : Credo AI, Holistic AI",
|
| 628 |
+
"BI executive : Tableau, Power BI, Looker"
|
| 629 |
+
],
|
| 630 |
+
"competences_soft": [
|
| 631 |
+
"Leadership stratégique et vision business",
|
| 632 |
+
"Capacité à influencer le C-level et à vendre la valeur des données",
|
| 633 |
+
"Gestion du changement et acculturation data",
|
| 634 |
+
"Communication claire et storytelling data",
|
| 635 |
+
"Gestion d’équipe pluridisciplinaire (data scientists, engineers, analysts)"
|
| 636 |
+
],
|
| 637 |
+
"niveau_etude": "Bac+5 (MBA, école d’ingénieur/commerce, Master data science) + expérience significative.",
|
| 638 |
+
"formations_utiles": [
|
| 639 |
+
"MBA ou executive education (INSEAD, HEC, etc.)",
|
| 640 |
+
"Certifications data governance (CDMP, DAMA)",
|
| 641 |
+
"Formations AI governance et compliance (AI Act, NIST)",
|
| 642 |
+
"Executive programs data leadership (MIT, Stanford)"
|
| 643 |
+
],
|
| 644 |
+
"experience_requise": "10+ ans en data/analytics, dont 3–5 ans en rôle de gouvernance/leadership data."
|
| 645 |
+
},
|
| 646 |
+
{
|
| 647 |
+
"id": "cao",
|
| 648 |
+
"nom": "Chief Analytics Officer (CAO)",
|
| 649 |
+
"categorie": "Gouvernance, qualité, protection",
|
| 650 |
+
"description": "Dirige la stratégie analytique et décisionnelle. Responsable de la création de valeur par l’analytics et l’IA prédictive.",
|
| 651 |
+
"missions_principales": [
|
| 652 |
+
"Définir la roadmap analytics et IA prédictive",
|
| 653 |
+
"Piloter les projets d’analytics avancés et ML",
|
| 654 |
+
"Mesurer l’impact business des insights et modèles",
|
| 655 |
+
"Construire et animer les équipes analytics/data science",
|
| 656 |
+
"Aligner les priorités analytics avec les objectifs stratégiques"
|
| 657 |
+
],
|
| 658 |
+
"competences_techniques": [
|
| 659 |
+
"Analytics avancé et machine learning",
|
| 660 |
+
"Gestion de portefeuille de projets data science",
|
| 661 |
+
"KPI analytics (ROI, précision modèles, adoption)",
|
| 662 |
+
"Roadmapping technique et priorisation",
|
| 663 |
+
"Cloud analytics (Databricks, Snowflake ML, BigQuery ML)"
|
| 664 |
+
],
|
| 665 |
+
"outils_technologies": [
|
| 666 |
+
"Cloud analytics platforms : Databricks, Snowflake ML, BigQuery ML",
|
| 667 |
+
"BI avancé : Looker, Tableau, Power BI",
|
| 668 |
+
"MLOps : MLflow, Vertex AI, SageMaker",
|
| 669 |
+
"Outils de gestion de projets data (Jira, Asana, Monday)"
|
| 670 |
+
],
|
| 671 |
+
"competences_soft": [
|
| 672 |
+
"Leadership technique et business",
|
| 673 |
+
"Storytelling analytics",
|
| 674 |
+
"Gestion d’équipe data science",
|
| 675 |
+
"Vision stratégique"
|
| 676 |
+
],
|
| 677 |
+
"niveau_etude": "Bac+5 (Master analytics/data science, école d’ingénieur/commerce).",
|
| 678 |
+
"formations_utiles": [
|
| 679 |
+
"Master analytics/data science",
|
| 680 |
+
"MBA ou executive education",
|
| 681 |
+
"Certifications cloud analytics"
|
| 682 |
+
],
|
| 683 |
+
"experience_requise": "8+ ans analytics/data science, dont 3+ ans leadership."
|
| 684 |
+
},
|
| 685 |
+
{
|
| 686 |
+
"id": "data_manager",
|
| 687 |
+
"nom": "Data Manager",
|
| 688 |
+
"categorie": "Gouvernance, qualité, protection",
|
| 689 |
+
"description": "Gère les ressources et processus data. Assure la qualité, la disponibilité et la conformité des données au quotidien.",
|
| 690 |
+
"missions_principales": [
|
| 691 |
+
"Gérer les inventaires de données et métadonnées",
|
| 692 |
+
"Assurer la qualité des données selon les standards",
|
| 693 |
+
"Piloter la conformité et les audits data",
|
| 694 |
+
"Coordonner les équipes data sur les projets transversaux",
|
| 695 |
+
"Optimiser les processus de gestion des données"
|
| 696 |
+
],
|
| 697 |
+
"competences_techniques": [
|
| 698 |
+
"Data quality frameworks et métriques",
|
| 699 |
+
"Data catalog et lineage",
|
| 700 |
+
"Conformité RGPD de base",
|
| 701 |
+
"SQL pour audits et contrôles qualité",
|
| 702 |
+
"Outils de data governance"
|
| 703 |
+
],
|
| 704 |
+
"outils_technologies": [
|
| 705 |
+
"Data catalogs : Collibra, Alation, DataHub",
|
| 706 |
+
"Data quality : Great Expectations, Soda Core",
|
| 707 |
+
"Excel/Google Sheets avancé",
|
| 708 |
+
"SQL, BI tools pour audits"
|
| 709 |
+
],
|
| 710 |
+
"competences_soft": [
|
| 711 |
+
"Organisation et gestion de projet",
|
| 712 |
+
"Collaboration transversale",
|
| 713 |
+
"Rigueur et sens du détail"
|
| 714 |
+
],
|
| 715 |
+
"niveau_etude": "Bac+3 à Bac+5 (informatique, gestion, commerce).",
|
| 716 |
+
"formations_utiles": [
|
| 717 |
+
"Formations data governance",
|
| 718 |
+
"Certifications RGPD/data management"
|
| 719 |
+
],
|
| 720 |
+
"experience_requise": "3–7 ans en data/analytique."
|
| 721 |
+
},
|
| 722 |
+
{
|
| 723 |
+
"id": "data_product_manager",
|
| 724 |
+
"nom": "Data Product Manager",
|
| 725 |
+
"categorie": "Produits, projets, stratégie",
|
| 726 |
+
"description": "Gère les produits data comme un produit business (data lake, data mart, API data, ML features). Définit la roadmap et la valeur métier.",
|
| 727 |
+
"missions_principales": [
|
| 728 |
+
"Définir la vision et roadmap du produit data",
|
| 729 |
+
"Prioriser les features selon l’impact business et les besoins des consommateurs data",
|
| 730 |
+
"Collaborer avec data engineers, analysts et métiers pour définir les spécifications",
|
| 731 |
+
"Mesurer l’adoption, l’usage et le ROI du produit data",
|
| 732 |
+
"Évangeliser le produit auprès des consommateurs internes"
|
| 733 |
+
],
|
| 734 |
+
"competences_technences": [
|
| 735 |
+
"Data product thinking (usage, adoption, métriques produit)",
|
| 736 |
+
"Compréhension technique de la stack data (warehouse, pipelines, quality)",
|
| 737 |
+
"Roadmapping et priorisation (RICE, Kano, etc.)",
|
| 738 |
+
"KPI data products (usage metrics, data freshness, adoption)",
|
| 739 |
+
"Data mesh et data productisation",
|
| 740 |
+
"SQL et BI pour valider les hypothèses"
|
| 741 |
+
],
|
| 742 |
+
"outils_technologies": [
|
| 743 |
+
"Roadmapping : Productboard, Aha!, Jira, Notion",
|
| 744 |
+
"Prototyping : Figma pour mockups de dashboards/API",
|
| 745 |
+
"Analytics : Mixpanel, Amplitude pour adoption data",
|
| 746 |
+
"BI : Looker, Tableau, Power BI",
|
| 747 |
+
"SQL, dbt pour valider les datasets",
|
| 748 |
+
"Data catalogs pour discovery"
|
| 749 |
+
],
|
| 750 |
+
"competences_soft": [
|
| 751 |
+
"Product mindset appliqué aux données",
|
| 752 |
+
"Communication avec métiers et data teams",
|
| 753 |
+
"Storytelling data product",
|
| 754 |
+
"Gestion de stakeholders multiples"
|
| 755 |
+
],
|
| 756 |
+
"niveau_etude": "Bac+5 (école commerce, Master data, école d’ingénieur).",
|
| 757 |
+
"formations_utiles": [
|
| 758 |
+
"Formations product management",
|
| 759 |
+
"Formations data product (Reforge, Product School)",
|
| 760 |
+
"Certifications product management"
|
| 761 |
+
],
|
| 762 |
+
"experience_requise": "3+ ans product management ou data analytics."
|
| 763 |
+
},
|
| 764 |
+
{
|
| 765 |
+
"id": "ai_product_manager",
|
| 766 |
+
"nom": "AI Product Manager",
|
| 767 |
+
"categorie": "Produits, projets, stratégie",
|
| 768 |
+
"description": "Gère les produits utilisant l’IA (chatbots, systèmes de recommandation, agents IA, outils GenAI). Aligne besoins métier et capacités techniques IA.",
|
| 769 |
+
"missions_principales": [
|
| 770 |
+
"Définir la roadmap produit IA",
|
| 771 |
+
"Prioriser features IA selon ROI, UX et faisabilité technique",
|
| 772 |
+
"Collaborer avec data scientists, ML engineers et design",
|
| 773 |
+
"Tester et mesurer l’efficacité des modèles IA (A/B testing, métriques produit)",
|
| 774 |
+
"Gérer les risques éthiques et réglementaires de l’IA"
|
| 775 |
+
],
|
| 776 |
+
"competences_techniques": [
|
| 777 |
+
"Compréhension des modèles ML/LLM (capacités, limites, coûts)",
|
| 778 |
+
"Patterns d’applications IA (RAG, agents, outils, fine-tuning)",
|
| 779 |
+
"Métriques produit IA (précision, latence, coût d’inférence, satisfaction user)",
|
| 780 |
+
"UX d’interactions IA (prompts, feedback loops)",
|
| 781 |
+
"Notions d’éthique IA et compliance AI Act"
|
| 782 |
+
],
|
| 783 |
+
"outils_technologies": [
|
| 784 |
+
"Productboard, Jira, Aha! pour roadmapping",
|
| 785 |
+
"Figma pour mockups d’interfaces IA",
|
| 786 |
+
"Amplitude, Mixpanel pour analytics IA",
|
| 787 |
+
"APIs LLM : OpenAI, Anthropic, Vertex AI",
|
| 788 |
+
"LangChain, LlamaIndex pour prototyping",
|
| 789 |
+
"Prompt engineering tools"
|
| 790 |
+
],
|
| 791 |
+
"competences_soft": [
|
| 792 |
+
"Product thinking appliqué à l’IA",
|
| 793 |
+
"Collaboration avec data scientists et ML engineers",
|
| 794 |
+
"Gestion des attentes sur les capacités IA",
|
| 795 |
+
"Sensibilité éthique et UX"
|
| 796 |
+
],
|
| 797 |
+
"niveau_etude": "Bac+5 (école commerce, Master IA/data, école d’ingénieur).",
|
| 798 |
+
"formations_utiles": [
|
| 799 |
+
"Formations product management IA",
|
| 800 |
+
"Bootcamps GenAI product",
|
| 801 |
+
"Certifications LLM/GenAI"
|
| 802 |
+
],
|
| 803 |
+
"experience_requise": "2–4 ans product management + appétence IA."
|
| 804 |
+
},
|
| 805 |
+
{
|
| 806 |
+
"id": "consultant_data",
|
| 807 |
+
"nom": "Consultant Data",
|
| 808 |
+
"categorie": "Produits, projets, stratégie",
|
| 809 |
+
"description": "Conseille les entreprises sur leur transformation data. Diagnostique, propose des architectures et accompagne la mise en œuvre.",
|
| 810 |
+
"missions_principales": [
|
| 811 |
+
"Diagnostiquer la maturité data de l’entreprise",
|
| 812 |
+
"Proposer des architectures data adaptées",
|
| 813 |
+
"Accompagner la mise en œuvre des projets data",
|
| 814 |
+
"Former les équipes aux bonnes pratiques",
|
| 815 |
+
"Mesurer l’impact des transformations data"
|
| 816 |
+
],
|
| 817 |
+
"competences_techniques": [
|
| 818 |
+
"Data maturity assessment",
|
| 819 |
+
"Architecture data (lakehouse, data mesh, data fabric)",
|
| 820 |
+
"Modern data stack",
|
| 821 |
+
"Cloud migration data",
|
| 822 |
+
"Data governance frameworks",
|
| 823 |
+
"ROI calculation pour projets data"
|
| 824 |
+
],
|
| 825 |
+
"outils_technologies": [
|
| 826 |
+
"Outils d’audit data (Collibra, Talend, Informatica)",
|
| 827 |
+
"Cloud platforms (AWS, GCP, Azure)",
|
| 828 |
+
"Modern data stack (dbt, Airflow, Snowflake, etc.)",
|
| 829 |
+
"Outils de modélisation (ER/Studio, Lucidchart)",
|
| 830 |
+
"Excel, Power BI pour business cases"
|
| 831 |
+
],
|
| 832 |
+
"competences_soft": [
|
| 833 |
+
"Consulting mindset",
|
| 834 |
+
"Communication claire et structurée",
|
| 835 |
+
"Gestion de projet et stakeholders",
|
| 836 |
+
"Pédagogie et formation"
|
| 837 |
+
],
|
| 838 |
+
"niveau_etude": "Bac+5 (école d’ingénieur, commerce, Master data).",
|
| 839 |
+
"formations_utiles": [
|
| 840 |
+
"Formations consulting data",
|
| 841 |
+
"Certifications cloud et data engineering",
|
| 842 |
+
"MBA ou mastère spécialisé"
|
| 843 |
+
],
|
| 844 |
+
"experience_requise": "3–7 ans en data/projects."
|
| 845 |
+
},
|
| 846 |
+
{
|
| 847 |
+
"id": "data_project_manager",
|
| 848 |
+
"nom": "Chef de Projet Data",
|
| 849 |
+
"categorie": "Produits, projets, stratégie",
|
| 850 |
+
"description": "Pilote les projets data de bout en bout. Gère les ressources, risques, budget et livraison des initiatives data.",
|
| 851 |
+
"missions_principales": [
|
| 852 |
+
"Planifier et piloter les projets data (scope, planning, budget)",
|
| 853 |
+
"Gérer les équipes pluridisciplinaires (data engineers, scientists, analysts)",
|
| 854 |
+
"Suivre les risques et les indicateurs de projet",
|
| 855 |
+
"Communiquer avec les sponsors et stakeholders",
|
| 856 |
+
"Assurer la livraison conforme aux attentes"
|
| 857 |
+
],
|
| 858 |
+
"competences_techniques": [
|
| 859 |
+
"Gestion de projet agile (Scrum, Kanban)",
|
| 860 |
+
"Compréhension technique data/IA",
|
| 861 |
+
"KPI projet data (data freshness, modèle accuracy, adoption)",
|
| 862 |
+
"Outils de gestion de projet",
|
| 863 |
+
"Notions de budget cloud data"
|
| 864 |
+
],
|
| 865 |
+
"outils_technologies": [
|
| 866 |
+
"Jira, Confluence, Asana, Monday.com",
|
| 867 |
+
"MS Project, Smartsheet",
|
| 868 |
+
"Power BI/Tableau pour dashboards projet",
|
| 869 |
+
"GitHub/GitLab pour suivi technique"
|
| 870 |
+
],
|
| 871 |
+
"competences_soft": [
|
| 872 |
+
"Leadership d’équipe",
|
| 873 |
+
"Gestion de stakeholders",
|
| 874 |
+
"Résolution de conflits",
|
| 875 |
+
"Communication claire"
|
| 876 |
+
],
|
| 877 |
+
"niveau_etude": "Bac+5 (management, informatique).",
|
| 878 |
+
"formations_utiles": [
|
| 879 |
+
"Certifications PMP, Scrum Master, Prince2",
|
| 880 |
+
"Formations gestion de projet data"
|
| 881 |
+
],
|
| 882 |
+
"experience_requise": "3–7 ans gestion de projet IT/data."
|
| 883 |
+
},
|
| 884 |
+
{
|
| 885 |
+
"id": "data_strategist",
|
| 886 |
+
"nom": "Data Strategist",
|
| 887 |
+
"categorie": "Produits, projets, stratégie",
|
| 888 |
+
"description": "Définit la stratégie data de l’entreprise. Identifie les opportunités data et aligne avec les objectifs business.",
|
| 889 |
+
"missions_principales": [
|
| 890 |
+
"Identifier les opportunités data stratégiques",
|
| 891 |
+
"Construire la roadmap data à 2–3 ans",
|
| 892 |
+
"Mesurer l’impact business des initiatives data",
|
| 893 |
+
"Conseiller le C-level sur les investissements data",
|
| 894 |
+
"Veille technologique et benchmarking data"
|
| 895 |
+
],
|
| 896 |
+
"competences_techniques": [
|
| 897 |
+
"Data strategy frameworks",
|
| 898 |
+
"Data maturity models",
|
| 899 |
+
"ROI calculation data projects",
|
| 900 |
+
"Benchmarking data stack",
|
| 901 |
+
"Trends data (data mesh, GenAI, real-time)"
|
| 902 |
+
],
|
| 903 |
+
"outils_technologies": [
|
| 904 |
+
"Excel, Google Sheets pour business cases",
|
| 905 |
+
"Power BI/Tableau pour présentations",
|
| 906 |
+
"Outils de veille (Gartner, Forrester)",
|
| 907 |
+
"Notion/Confluence pour documentation"
|
| 908 |
+
],
|
| 909 |
+
"competences_soft": [
|
| 910 |
+
"Vision stratégique",
|
| 911 |
+
"Communication C-level",
|
| 912 |
+
"Storytelling business",
|
| 913 |
+
"Benchmarking"
|
| 914 |
+
],
|
| 915 |
+
"niveau_etude": "Bac+5 (MBA, école commerce, Master data).",
|
| 916 |
+
"formations_utiles": [
|
| 917 |
+
"MBA, executive education",
|
| 918 |
+
"Formations data strategy"
|
| 919 |
+
],
|
| 920 |
+
"experience_requise": "7+ ans data, 3+ ans stratégie."
|
| 921 |
+
},
|
| 922 |
+
{
|
| 923 |
+
"id": "data_steward",
|
| 924 |
+
"nom": "Data Steward",
|
| 925 |
+
"categorie": "Gouvernance, qualité, protection",
|
| 926 |
+
"description": "Assure la qualité et la gouvernance des données au quotidien. Documente, contrôle et corrige les données.",
|
| 927 |
+
"missions_principales": [
|
| 928 |
+
"Contrôler la qualité des données selon les règles métier",
|
| 929 |
+
"Documenter les métadonnées et glossaires",
|
| 930 |
+
"Corriger les anomalies et erreurs détectées",
|
| 931 |
+
"Former les contributeurs aux bonnes pratiques data",
|
| 932 |
+
"Participer aux audits qualité"
|
| 933 |
+
],
|
| 934 |
+
"competences_techniques": [
|
| 935 |
+
"Data quality rules et métriques",
|
| 936 |
+
"Métadonnées et glossaires",
|
| 937 |
+
"SQL pour contrôles qualité",
|
| 938 |
+
"Excel/Google Sheets avancé",
|
| 939 |
+
"Outils data quality"
|
| 940 |
+
],
|
| 941 |
+
"outils_technologies": [
|
| 942 |
+
"Excel, Google Sheets",
|
| 943 |
+
"SQL",
|
| 944 |
+
"Data quality tools (Great Expectations, Soda)",
|
| 945 |
+
"Data catalogs (DataHub, Amundsen)",
|
| 946 |
+
"Jira pour tracking des issues data"
|
| 947 |
+
],
|
| 948 |
+
"competences_soft": [
|
| 949 |
+
"Rigueur et attention aux détails",
|
| 950 |
+
"Collaboration avec métiers et IT",
|
| 951 |
+
"Pédagogie",
|
| 952 |
+
"Organisation"
|
| 953 |
+
],
|
| 954 |
+
"niveau_etude": "Bac+2 à Bac+3.",
|
| 955 |
+
"formations_utiles": [
|
| 956 |
+
"Formations data quality/governance",
|
| 957 |
+
"Certifications data stewardship"
|
| 958 |
+
],
|
| 959 |
+
"experience_requise": "1–3 ans data/analytique."
|
| 960 |
+
}
|
| 961 |
+
]
|
| 962 |
+
},
|
| 963 |
+
{
|
| 964 |
+
"metiers": [
|
| 965 |
+
{
|
| 966 |
+
"id": "nlp_engineer",
|
| 967 |
+
"nom": "NLP Engineer / Ingénieur TAL",
|
| 968 |
+
"categorie": "Spécialistes techniques IA",
|
| 969 |
+
"description": "Développe des solutions de traitement automatique du langage naturel (chatbots, analyse de sentiments, NER, traduction, résumé).",
|
| 970 |
+
"missions_principales": [
|
| 971 |
+
"Construire des pipelines NLP end-to-end (préprocessing, embedding, modèles, postprocessing)",
|
| 972 |
+
"Fine-tuner des modèles de langage pour des cas d’usage spécifiques",
|
| 973 |
+
"Implémenter des patterns RAG et agents conversationnels",
|
| 974 |
+
"Optimiser la performance et la latence des systèmes NLP",
|
| 975 |
+
"Évaluer la qualité des modèles NLP (BLEU, ROUGE, perplexity, human eval)"
|
| 976 |
+
],
|
| 977 |
+
"competences_techniques": [
|
| 978 |
+
"Maîtrise des modèles transformers et LLMs",
|
| 979 |
+
"Fine-tuning et PEFT (LoRA, QLoRA)",
|
| 980 |
+
"RAG (retrieval-augmented generation) et vector search",
|
| 981 |
+
"NLP evaluation metrics (BLEU, ROUGE, BERTScore, perplexity)",
|
| 982 |
+
"Embeddings et vector databases",
|
| 983 |
+
"Prompt engineering et chain-of-thought",
|
| 984 |
+
"Langage spécifique (NER, POS tagging, dependency parsing)"
|
| 985 |
+
],
|
| 986 |
+
"outils_technologies": [
|
| 987 |
+
"Hugging Face Transformers, PEFT",
|
| 988 |
+
"spaCy, NLTK, Stanford CoreNLP",
|
| 989 |
+
"LangChain, LlamaIndex, Haystack",
|
| 990 |
+
"Vector DB : Pinecone, Weaviate, Qdrant, PGVector",
|
| 991 |
+
"APIs : OpenAI GPT, Anthropic Claude, Google Gemini, Cohere",
|
| 992 |
+
"FastAPI pour APIs NLP",
|
| 993 |
+
"Docker/Kubernetes pour déploiement"
|
| 994 |
+
],
|
| 995 |
+
"competences_soft": [
|
| 996 |
+
"Compréhension linguistique",
|
| 997 |
+
"Curiosité pour les langues et cultures",
|
| 998 |
+
"Collaboration avec UX et métiers"
|
| 999 |
+
],
|
| 1000 |
+
"niveau_etude": "Bac+5 (Master IA/NLP, école d’ingénieur).",
|
| 1001 |
+
"formations_utiles": [
|
| 1002 |
+
"Master spécialisation NLP",
|
| 1003 |
+
"Formations Hugging Face, spaCy",
|
| 1004 |
+
"MOOCs NLP avancés"
|
| 1005 |
+
],
|
| 1006 |
+
"experience_requise": "1–3 ans NLP/ML."
|
| 1007 |
+
},
|
| 1008 |
+
{
|
| 1009 |
+
"id": "cv_engineer",
|
| 1010 |
+
"nom": "Computer Vision Engineer",
|
| 1011 |
+
"categorie": "Spécialistes techniques IA",
|
| 1012 |
+
"description": "Développe des solutions d’analyse d’images et vidéos (détection d’objets, segmentation, tracking, 3D reconstruction).",
|
| 1013 |
+
"missions_principales": [
|
| 1014 |
+
"Construire des pipelines vision end-to-end",
|
| 1015 |
+
"Fine-tuner des modèles de vision (YOLO, DETR, Segment Anything)",
|
| 1016 |
+
"Optimiser pour edge devices et temps réel",
|
| 1017 |
+
"Implémenter tracking et multi-object tracking",
|
| 1018 |
+
"Évaluer la précision (mAP, IoU, FPS)"
|
| 1019 |
+
],
|
| 1020 |
+
"competences_techniques": [
|
| 1021 |
+
"CNN et architectures vision (ResNet, EfficientNet, Vision Transformers)",
|
| 1022 |
+
"Object detection (YOLOv8, YOLOv9, RT-DETR)",
|
| 1023 |
+
"Segmentation (Mask R-CNN, SAM, U-Net)",
|
| 1024 |
+
"Tracking (DeepSORT, ByteTrack)",
|
| 1025 |
+
"Optimisation edge (TensorRT, ONNX Runtime, TFLite)",
|
| 1026 |
+
"3D vision de base (depth estimation, point clouds)"
|
| 1027 |
+
],
|
| 1028 |
+
"outils_technologies": [
|
| 1029 |
+
"PyTorch, TensorFlow, OpenCV",
|
| 1030 |
+
"Ultralytics YOLO, Detectron2, MMdetection",
|
| 1031 |
+
"Segment Anything Model (SAM)",
|
| 1032 |
+
"ONNX, TensorRT, OpenVINO",
|
| 1033 |
+
"Docker pour déploiement edge",
|
| 1034 |
+
"Roboflow pour datasets et annotation"
|
| 1035 |
+
],
|
| 1036 |
+
"competences_soft": [
|
| 1037 |
+
"Sens visuel",
|
| 1038 |
+
"Rigueur dans l’évaluation",
|
| 1039 |
+
"Collaboration avec hardware"
|
| 1040 |
+
],
|
| 1041 |
+
"niveau_etude": "Bac+5 (Master vision/IA).",
|
| 1042 |
+
"formations_utiles": [
|
| 1043 |
+
"Formations CV avancées",
|
| 1044 |
+
"Certifications NVIDIA"
|
| 1045 |
+
],
|
| 1046 |
+
"experience_requise": "1–3 ans CV/ML."
|
| 1047 |
+
},
|
| 1048 |
+
{
|
| 1049 |
+
"id": "llm_engineer",
|
| 1050 |
+
"nom": "LLM Engineer",
|
| 1051 |
+
"categorie": "Spécialistes techniques IA",
|
| 1052 |
+
"description": "Spécialiste des Large Language Models. Fine-tune, déploie et optimise des modèles de langage pour des cas d’usage spécifiques.",
|
| 1053 |
+
"missions_principales": [
|
| 1054 |
+
"Fine-tuner des LLMs open-source (Llama, Mistral, Mixtral)",
|
| 1055 |
+
"Implémenter RAG, agents, tools et memory",
|
| 1056 |
+
"Optimiser coûts et latence (quantization, distillation)",
|
| 1057 |
+
"Évaluer qualité (human eval, LLM-as-judge)",
|
| 1058 |
+
"Déployer en production (APIs, streaming, edge)"
|
| 1059 |
+
],
|
| 1060 |
+
"competences_techniques": [
|
| 1061 |
+
"Fine-tuning (PEFT, LoRA, QLoRA)",
|
| 1062 |
+
"RAG (advanced retrieval, reranking, hybrid search)",
|
| 1063 |
+
"Agents LLM (tools calling, reasoning)",
|
| 1064 |
+
"Quantization (bitsandbytes, GPTQ, AWQ)",
|
| 1065 |
+
"Evaluation LLM (human eval, LLM-as-judge, benchmarks)",
|
| 1066 |
+
"Prompt engineering avancé"
|
| 1067 |
+
],
|
| 1068 |
+
"outils_technologies": [
|
| 1069 |
+
"Hugging Face (Transformers, TRL, PEFT)",
|
| 1070 |
+
"Unsloth, Axolotl pour fine-tuning rapide",
|
| 1071 |
+
"LangChain, LlamaIndex, Haystack",
|
| 1072 |
+
"vLLM, Text Generation Inference pour serving",
|
| 1073 |
+
"Vector DB : Pinecone, Weaviate, Milvus",
|
| 1074 |
+
"Ollama, LM Studio pour local eval"
|
| 1075 |
+
],
|
| 1076 |
+
"competences_soft": [
|
| 1077 |
+
"Rapidité d’apprentissage",
|
| 1078 |
+
"Expérimentation",
|
| 1079 |
+
"Collaboration"
|
| 1080 |
+
],
|
| 1081 |
+
"niveau_etude": "Bac+5 (Master IA/NLP).",
|
| 1082 |
+
"formations_utiles": [
|
| 1083 |
+
"Formations Hugging Face LLM",
|
| 1084 |
+
"Bootcamps LLM engineering"
|
| 1085 |
+
],
|
| 1086 |
+
"experience_requise": "1–2 ans ML/NLP."
|
| 1087 |
+
},
|
| 1088 |
+
{
|
| 1089 |
+
"id": "prompt_engineer",
|
| 1090 |
+
"nom": "Prompt Engineer",
|
| 1091 |
+
"categorie": "Spécialistes techniques IA",
|
| 1092 |
+
"description": "Optimise les prompts et interactions avec les LLMs pour maximiser la qualité et la cohérence des réponses.",
|
| 1093 |
+
"missions_principales": [
|
| 1094 |
+
"Concevoir des prompts optimisés pour différents LLMs",
|
| 1095 |
+
"Tester et itérer sur les prompts (A/B testing)",
|
| 1096 |
+
"Créer des templates et patterns réutilisables",
|
| 1097 |
+
"Former les équipes aux bonnes pratiques de prompting",
|
| 1098 |
+
"Mesurer l’efficacité des prompts (qualité, coût, latence)"
|
| 1099 |
+
],
|
| 1100 |
+
"competences_techniques": [
|
| 1101 |
+
"Prompt engineering techniques (chain-of-thought, few-shot, tree-of-thoughts)",
|
| 1102 |
+
"Compréhension des capacités/limites des différents LLMs",
|
| 1103 |
+
"A/B testing de prompts",
|
| 1104 |
+
"Métriques d’évaluation (human eval, LLM-as-judge)"
|
| 1105 |
+
],
|
| 1106 |
+
"outils_technologies": [
|
| 1107 |
+
"Promptfoo, LangSmith, PromptLayer",
|
| 1108 |
+
"OpenAI Playground, Anthropic Console",
|
| 1109 |
+
"LangChain, LlamaIndex pour templating",
|
| 1110 |
+
"Jupyter pour experimentation"
|
| 1111 |
+
],
|
| 1112 |
+
"competences_soft": [
|
| 1113 |
+
"Créativité linguistique",
|
| 1114 |
+
"Sens analytique",
|
| 1115 |
+
"Pédagogie"
|
| 1116 |
+
],
|
| 1117 |
+
"niveau_etude": "Bac+3 minimum.",
|
| 1118 |
+
"formations_utiles": [
|
| 1119 |
+
"Bootcamps prompt engineering",
|
| 1120 |
+
"Cours OpenAI/Anthropic"
|
| 1121 |
+
],
|
| 1122 |
+
"experience_requise": "0–1 an."
|
| 1123 |
+
},
|
| 1124 |
+
{
|
| 1125 |
+
"id": "data_viz_specialist",
|
| 1126 |
+
"nom": "Data Visualisation Specialist",
|
| 1127 |
+
"categorie": "Visualisation, décisionnel, métier",
|
| 1128 |
+
"description": "Crée des visualisations impactantes et narratives pour communiquer les insights data de manière accessible et persuasive.",
|
| 1129 |
+
"missions_principales": [
|
| 1130 |
+
"Concevoir des visualisations adaptées au message et au public",
|
| 1131 |
+
"Créer des dashboards interactifs et storytelling data",
|
| 1132 |
+
"Optimiser l’UX et l’accessibilité des visualisations",
|
| 1133 |
+
"Collaborer avec data analysts et métiers",
|
| 1134 |
+
"Produire des présentations data impactantes"
|
| 1135 |
+
],
|
| 1136 |
+
"competences_techniques": [
|
| 1137 |
+
"Principes de data visualization (Tufte, Cleveland)",
|
| 1138 |
+
"Design graphique et UX/UI pour data",
|
| 1139 |
+
"Utilisation avancée des outils de visualisation",
|
| 1140 |
+
"SQL pour préparation des datasets viz",
|
| 1141 |
+
"Animation et interactivité data"
|
| 1142 |
+
],
|
| 1143 |
+
"outils_technologies": [
|
| 1144 |
+
"Tableau, Power BI, Looker",
|
| 1145 |
+
"D3.js, Observable, Vega-Lite",
|
| 1146 |
+
"Figma pour design",
|
| 1147 |
+
"Flourish, Datawrapper pour storytelling",
|
| 1148 |
+
"ggplot2, Plotly pour Python/R"
|
| 1149 |
+
],
|
| 1150 |
+
"competences_soft": [
|
| 1151 |
+
"Sens esthétique et créativité",
|
| 1152 |
+
"Storytelling data",
|
| 1153 |
+
"Compréhension du public cible"
|
| 1154 |
+
],
|
| 1155 |
+
"niveau_etude": "Bac+3 (design, informatique).",
|
| 1156 |
+
"formations_utiles": [
|
| 1157 |
+
"Formations data visualization",
|
| 1158 |
+
"Certifications Tableau/Power BI"
|
| 1159 |
+
],
|
| 1160 |
+
"experience_requise": "1–3 ans."
|
| 1161 |
+
},
|
| 1162 |
+
{
|
| 1163 |
+
"id": "data_architect",
|
| 1164 |
+
"nom": "Data Architect",
|
| 1165 |
+
"categorie": "Infrastructure et sécurité",
|
| 1166 |
+
"description": "Conçoit l’architecture globale des systèmes de données (data lake, warehouse, pipelines, gouvernance) pour répondre aux besoins actuels et futurs.",
|
| 1167 |
+
"missions_principales": [
|
| 1168 |
+
"Définir l’architecture data stratégique",
|
| 1169 |
+
"Choisir les technologies adaptées (lakehouse vs warehouse, on-prem vs cloud)",
|
| 1170 |
+
"Modéliser les données (data modeling, schemas)",
|
| 1171 |
+
"Garantir scalabilité, performance, coûts",
|
| 1172 |
+
"Documenter l’architecture et standards"
|
| 1173 |
+
],
|
| 1174 |
+
"competences_techniques": [
|
| 1175 |
+
"Architecture lakehouse/data mesh/data fabric",
|
| 1176 |
+
"Modélisation de données (étoile, data vault, Kimball)",
|
| 1177 |
+
"Benchmarking technologies data",
|
| 1178 |
+
"Optimisation coûts/performance cloud",
|
| 1179 |
+
"Data governance architecture"
|
| 1180 |
+
],
|
| 1181 |
+
"outils_technologies": [
|
| 1182 |
+
"Snowflake, Databricks, BigQuery",
|
| 1183 |
+
"Terraform, Lucidchart, dbt",
|
| 1184 |
+
"Collibra, Alation pour gouvernance"
|
| 1185 |
+
],
|
| 1186 |
+
"competences_soft": [
|
| 1187 |
+
"Vision stratégique",
|
| 1188 |
+
"Communication technique/business",
|
| 1189 |
+
"Benchmarking"
|
| 1190 |
+
],
|
| 1191 |
+
"niveau_etude": "Bac+5 (école d’ingénieur).",
|
| 1192 |
+
"formations_utiles": [
|
| 1193 |
+
"Certifications architecte cloud",
|
| 1194 |
+
"Formations data modeling"
|
| 1195 |
+
],
|
| 1196 |
+
"experience_requise": "5+ ans data engineering."
|
| 1197 |
+
},
|
| 1198 |
+
{
|
| 1199 |
+
"id": "ai_ethics_specialist",
|
| 1200 |
+
"nom": "AI Ethics Specialist",
|
| 1201 |
+
"categorie": "Formation, accompagnement, éthique",
|
| 1202 |
+
"description": "Assure l’éthique et la conformité des systèmes IA. Évalue les risques (bias, fairness, privacy) et propose des solutions responsables.",
|
| 1203 |
+
"missions_principales": [
|
| 1204 |
+
"Auditer les modèles IA (bias, fairness, robustness)",
|
| 1205 |
+
"Implémenter des garde-fous et mitigations",
|
| 1206 |
+
"Rédiger model cards et documentation éthique",
|
| 1207 |
+
"Former aux bonnes pratiques IA responsable",
|
| 1208 |
+
"Conseiller sur AI Act et conformité"
|
| 1209 |
+
],
|
| 1210 |
+
"competences_techniques": [
|
| 1211 |
+
"Bias detection et mitigation",
|
| 1212 |
+
"Fairness metrics (demographic parity, equal opportunity)",
|
| 1213 |
+
"Explainability (SHAP, LIME, counterfactuals)",
|
| 1214 |
+
"AI Act classification et obligations",
|
| 1215 |
+
"Privacy (DP-SGD, federated learning)"
|
| 1216 |
+
],
|
| 1217 |
+
"outils_technologies": [
|
| 1218 |
+
"IBM AI Fairness 360, Aequitas",
|
| 1219 |
+
"SHAP, LIME pour explainability",
|
| 1220 |
+
"Credo AI, Holistic AI pour gouvernance",
|
| 1221 |
+
"What-If Tool (Google), Facets",
|
| 1222 |
+
"AI FactSheets 360"
|
| 1223 |
+
],
|
| 1224 |
+
"competences_soft": [
|
| 1225 |
+
"Pensée critique éthique",
|
| 1226 |
+
"Communication interdisciplinaire",
|
| 1227 |
+
"Pédagogie"
|
| 1228 |
+
],
|
| 1229 |
+
"niveau_etude": "Bac+5 (éthique, droit, informatique).",
|
| 1230 |
+
"formations_utiles": [
|
| 1231 |
+
"Certifications AI ethics/governance",
|
| 1232 |
+
"Formations AI Act"
|
| 1233 |
+
],
|
| 1234 |
+
"experience_requise": "2–5 ans IA/compliance."
|
| 1235 |
+
},
|
| 1236 |
+
{
|
| 1237 |
+
"id": "dpo",
|
| 1238 |
+
"nom": "Data Protection Officer (DPO)",
|
| 1239 |
+
"categorie": "Gouvernance, qualité, protection",
|
| 1240 |
+
"description": "Garantit la conformité RGPD et protection des données personnelles dans les projets data/IA.",
|
| 1241 |
+
"missions_principales": [
|
| 1242 |
+
"Effectuer les DPIA (Data Protection Impact Assessment)",
|
| 1243 |
+
"Gérer les demandes d’exercice des droits (droit à l’oubli, etc.)",
|
| 1244 |
+
"Conseiller sur la conformité des traitements",
|
| 1245 |
+
"Auditer les systèmes de données",
|
| 1246 |
+
"Former aux obligations RGPD"
|
| 1247 |
+
],
|
| 1248 |
+
"competences_techniques": [
|
| 1249 |
+
"RGPD, CNIL, ePrivacy",
|
| 1250 |
+
"Data mapping et register of processing",
|
| 1251 |
+
"DPIA, transferts internationaux",
|
| 1252 |
+
"Sécurité des données (encryption, pseudonymisation)"
|
| 1253 |
+
],
|
| 1254 |
+
"outils_technologies": [
|
| 1255 |
+
"Outils compliance (OneTrust, TrustArc)",
|
| 1256 |
+
"Excel pour data mapping",
|
| 1257 |
+
"Outils de chiffrement"
|
| 1258 |
+
],
|
| 1259 |
+
"competences_soft": [
|
| 1260 |
+
"Rigueur juridique",
|
| 1261 |
+
"Communication",
|
| 1262 |
+
"Pédagogie"
|
| 1263 |
+
],
|
| 1264 |
+
"niveau_etude": "Bac+5 (droit, gestion).",
|
| 1265 |
+
"formations_utiles": [
|
| 1266 |
+
"Certifications DPO/RGPD"
|
| 1267 |
+
],
|
| 1268 |
+
"experience_requise": "3+ ans compliance."
|
| 1269 |
+
}
|
| 1270 |
+
]
|
| 1271 |
+
}
|
| 1272 |
+
]
|
| 1273 |
+
}
|
src/parser_flow/CV_agent_flow.py
CHANGED
|
@@ -1,110 +1,237 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import json
|
| 2 |
import logging
|
| 3 |
import os
|
| 4 |
import yaml
|
| 5 |
import asyncio
|
| 6 |
from datetime import datetime
|
| 7 |
-
from typing import Dict, Any
|
|
|
|
| 8 |
from crewai import Agent, Task, Crew, Process
|
| 9 |
from src.config.app_config import get_small_llm, get_big_llm
|
| 10 |
|
| 11 |
logger = logging.getLogger(__name__)
|
| 12 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
|
| 14 |
class CVAgentOrchestrator:
|
| 15 |
-
|
|
|
|
| 16 |
def __init__(self):
|
| 17 |
self.llm = get_small_llm()
|
| 18 |
self.big_llm = get_big_llm()
|
| 19 |
self.agents_config = self._load_yaml("agents.yaml")
|
| 20 |
self.tasks_config = self._load_yaml("tasks.yaml")
|
|
|
|
|
|
|
| 21 |
self._create_agents()
|
| 22 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
def _load_yaml(self, filename: str) -> Dict:
|
| 24 |
-
base_path = os.path.dirname(os.path.dirname(__file__))
|
| 25 |
config_path = os.path.join(base_path, "config", filename)
|
| 26 |
-
with open(config_path,
|
| 27 |
return yaml.safe_load(f)
|
| 28 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
def _create_agents(self):
|
| 30 |
def make_agent(name, llm_override=None):
|
| 31 |
return Agent(
|
| 32 |
config=self.agents_config[name],
|
| 33 |
llm=llm_override or self.llm,
|
| 34 |
allow_delegation=False,
|
| 35 |
-
verbose=
|
| 36 |
max_iter=1,
|
| 37 |
-
respect_context_window=True
|
|
|
|
|
|
|
| 38 |
)
|
| 39 |
|
| 40 |
-
|
| 41 |
-
self.
|
| 42 |
-
self.
|
| 43 |
-
self.
|
| 44 |
-
self.
|
| 45 |
-
self.
|
| 46 |
-
self.
|
| 47 |
-
self.
|
| 48 |
-
self.
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
""
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 60 |
)
|
|
|
|
|
|
|
| 61 |
crew = Crew(
|
| 62 |
agents=[self.cv_splitter],
|
| 63 |
tasks=[task],
|
| 64 |
process=Process.sequential,
|
| 65 |
-
verbose=False
|
| 66 |
)
|
| 67 |
result = await crew.kickoff_async()
|
| 68 |
-
parsed = self._parse_json_output(result, default_structure={})
|
| 69 |
return parsed
|
| 70 |
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 75 |
def create_task_async(task_key, agent, **kwargs):
|
| 76 |
t_config = self.tasks_config[task_key].copy()
|
| 77 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 78 |
task = Task(config=t_config, agent=agent)
|
| 79 |
c = Crew(agents=[agent], tasks=[task], verbose=False)
|
| 80 |
return (task_key, c.kickoff_async())
|
| 81 |
|
| 82 |
tasks_def = [
|
| 83 |
-
(
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
(
|
| 104 |
-
|
| 105 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 106 |
]
|
| 107 |
-
task_coroutines = [create_task_async(key, agent, **kwargs) for key, agent, kwargs in tasks_def]
|
| 108 |
keys = [t[0] for t in task_coroutines]
|
| 109 |
coroutines = [t[1] for t in task_coroutines]
|
| 110 |
results_list = await asyncio.gather(*coroutines, return_exceptions=True)
|
|
@@ -116,117 +243,600 @@ class CVAgentOrchestrator:
|
|
| 116 |
else:
|
| 117 |
results_map[key] = result
|
| 118 |
|
| 119 |
-
return self.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 120 |
|
| 121 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 122 |
|
| 123 |
def get_parsed(key, default=None):
|
| 124 |
if key not in results_map:
|
| 125 |
return default
|
| 126 |
return self._parse_json_output(results_map[key], default)
|
| 127 |
|
| 128 |
-
competences = get_parsed(
|
| 129 |
-
experiences = get_parsed(
|
| 130 |
-
projets = get_parsed(
|
| 131 |
-
formations = get_parsed(
|
| 132 |
-
reconversion = get_parsed(
|
| 133 |
-
|
|
|
|
|
|
|
| 134 |
latest_end_date = etudiant_data.get("latest_education_end_date")
|
| 135 |
if latest_end_date:
|
| 136 |
is_student_by_date = self._is_still_student(latest_end_date)
|
| 137 |
etudiant_data["is_etudiant"] = is_student_by_date
|
| 138 |
|
| 139 |
-
langues_raw = get_parsed(
|
| 140 |
|
| 141 |
if isinstance(competences, dict):
|
| 142 |
-
# Deduplicate hard_skills while preserving order
|
| 143 |
raw_skills = competences.get("hard_skills", [])
|
| 144 |
seen = set()
|
| 145 |
unique_skills = []
|
| 146 |
for skill in raw_skills:
|
| 147 |
-
key =
|
|
|
|
|
|
|
|
|
|
|
|
|
| 148 |
if key not in seen:
|
| 149 |
seen.add(key)
|
| 150 |
unique_skills.append(skill)
|
| 151 |
competences["hard_skills"] = unique_skills
|
| 152 |
|
| 153 |
-
identity = get_parsed(
|
| 154 |
|
| 155 |
return {
|
| 156 |
"candidat": {
|
| 157 |
-
"first_name":
|
|
|
|
|
|
|
|
|
|
|
|
|
| 158 |
"compétences": competences,
|
| 159 |
"expériences": experiences,
|
| 160 |
"reconversion": reconversion,
|
| 161 |
"projets": projets,
|
| 162 |
"formations": formations,
|
| 163 |
"etudiant": etudiant_data,
|
| 164 |
-
"langues":
|
|
|
|
|
|
|
|
|
|
|
|
|
| 165 |
}
|
| 166 |
}
|
| 167 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 168 |
def _is_still_student(self, date_str: str) -> bool:
|
|
|
|
| 169 |
if not date_str:
|
| 170 |
return False
|
| 171 |
date_str = str(date_str).lower().strip()
|
| 172 |
-
ongoing_keywords = [
|
|
|
|
|
|
|
| 173 |
if any(keyword in date_str for keyword in ongoing_keywords):
|
| 174 |
return True
|
| 175 |
-
|
| 176 |
try:
|
| 177 |
now = datetime.now()
|
| 178 |
end_date = None
|
| 179 |
-
if len(date_str) == 10 and date_str[4] ==
|
| 180 |
-
|
| 181 |
-
elif len(date_str) == 7 and date_str[4] ==
|
| 182 |
-
|
| 183 |
-
elif
|
| 184 |
-
parts = date_str.split(
|
| 185 |
if len(parts) == 2:
|
| 186 |
-
|
| 187 |
if len(y) == 4:
|
| 188 |
-
|
| 189 |
elif len(y) == 2:
|
| 190 |
-
|
| 191 |
-
|
| 192 |
elif len(date_str) == 4 and date_str.isdigit():
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
| 196 |
if end_date:
|
| 197 |
return end_date >= now
|
| 198 |
-
|
| 199 |
return False
|
| 200 |
-
|
| 201 |
except (ValueError, IndexError):
|
| 202 |
logger.warning(f"Date parsing failed for: {date_str}")
|
| 203 |
return False
|
| 204 |
|
| 205 |
def _parse_json_output(self, crew_output, default_structure=None) -> Any:
|
| 206 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 207 |
|
| 208 |
-
|
| 209 |
-
|
| 210 |
-
|
| 211 |
-
|
|
|
|
| 212 |
if len(parts) >= 3:
|
| 213 |
raw = parts[1].strip()
|
| 214 |
|
| 215 |
-
|
| 216 |
-
raw = raw.strip().lstrip('\ufeff') # BOM
|
| 217 |
|
| 218 |
-
|
| 219 |
-
|
| 220 |
-
|
| 221 |
-
|
| 222 |
-
|
| 223 |
-
|
| 224 |
-
|
|
|
|
|
|
|
| 225 |
if start_idx != -1 and end_idx > start_idx:
|
| 226 |
try:
|
| 227 |
-
return json.loads(
|
| 228 |
except json.JSONDecodeError:
|
| 229 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 230 |
|
| 231 |
-
|
| 232 |
-
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Orchestrateur CV enrichi avec 3 phases :
|
| 3 |
+
Phase 1 : Découpage du CV en sections
|
| 4 |
+
Phase 2 : Extraction parallèle (8 agents existants)
|
| 5 |
+
Phase 3 : Analyse & Recommandation parallèle (5 nouveaux agents)
|
| 6 |
+
|
| 7 |
+
Produit un JSON en 2 parties : informations + recommandations.
|
| 8 |
+
"""
|
| 9 |
+
|
| 10 |
import json
|
| 11 |
import logging
|
| 12 |
import os
|
| 13 |
import yaml
|
| 14 |
import asyncio
|
| 15 |
from datetime import datetime
|
| 16 |
+
from typing import Dict, Any, List
|
| 17 |
+
|
| 18 |
from crewai import Agent, Task, Crew, Process
|
| 19 |
from src.config.app_config import get_small_llm, get_big_llm
|
| 20 |
|
| 21 |
logger = logging.getLogger(__name__)
|
| 22 |
|
| 23 |
+
#_____________________________________________________________________________________
|
| 24 |
+
|
| 25 |
+
# Configuration du logger pour capturer la verbosité dans un fichier
|
| 26 |
+
verbose_logger = logging.getLogger("crewai_verbose")
|
| 27 |
+
verbose_logger.setLevel(logging.INFO)
|
| 28 |
+
|
| 29 |
+
# Création du fichier de log (écrase le précédent à chaque run avec 'w')
|
| 30 |
+
file_handler = logging.FileHandler("agents_trace.log", mode='w', encoding='utf-8')
|
| 31 |
+
formatter = logging.Formatter('%(asctime)s - %(message)s')
|
| 32 |
+
file_handler.setFormatter(formatter)
|
| 33 |
+
verbose_logger.addHandler(file_handler)
|
| 34 |
|
| 35 |
class CVAgentOrchestrator:
|
| 36 |
+
"""Orchestrateur multi-agents pour le parsing et l'analyse de CV."""
|
| 37 |
+
|
| 38 |
def __init__(self):
|
| 39 |
self.llm = get_small_llm()
|
| 40 |
self.big_llm = get_big_llm()
|
| 41 |
self.agents_config = self._load_yaml("agents.yaml")
|
| 42 |
self.tasks_config = self._load_yaml("tasks.yaml")
|
| 43 |
+
self.metiers_data = self._load_metiers()
|
| 44 |
+
self.skill_domain_map = self._load_skill_domain_map()
|
| 45 |
self._create_agents()
|
| 46 |
|
| 47 |
+
# ──────────────────────────────────────────────
|
| 48 |
+
# Chargement des configurations
|
| 49 |
+
# ──────────────────────────────────────────────
|
| 50 |
+
|
| 51 |
def _load_yaml(self, filename: str) -> Dict:
|
| 52 |
+
base_path = os.path.dirname(os.path.dirname(__file__))
|
| 53 |
config_path = os.path.join(base_path, "config", filename)
|
| 54 |
+
with open(config_path, "r", encoding="utf-8") as f:
|
| 55 |
return yaml.safe_load(f)
|
| 56 |
|
| 57 |
+
def _load_metiers(self) -> List[Dict]:
|
| 58 |
+
"""Charge le référentiel de métiers (sans les embeddings pour économiser la mémoire)."""
|
| 59 |
+
base_path = os.path.dirname(os.path.dirname(__file__))
|
| 60 |
+
metiers_path = os.path.join(base_path, "data", "metiers.json")
|
| 61 |
+
with open(metiers_path, "r", encoding="utf-8") as f:
|
| 62 |
+
data = json.load(f)
|
| 63 |
+
metiers = []
|
| 64 |
+
for m in data.get("metiers", []):
|
| 65 |
+
clean = {k: v for k, v in m.items() if k != "embedding"}
|
| 66 |
+
metiers.append(clean)
|
| 67 |
+
return metiers
|
| 68 |
+
|
| 69 |
+
def _load_skill_domain_map(self) -> Dict[str, List[str]]:
|
| 70 |
+
"""Charge le mapping compétences -> domaines."""
|
| 71 |
+
base_path = os.path.dirname(os.path.dirname(__file__))
|
| 72 |
+
map_path = os.path.join(base_path, "config", "skill_domain_map.json")
|
| 73 |
+
with open(map_path, "r", encoding="utf-8") as f:
|
| 74 |
+
return json.load(f)
|
| 75 |
+
|
| 76 |
+
# ──────────────────────────────────────────────
|
| 77 |
+
# Création des agents
|
| 78 |
+
# ──────────────────────────────────────────────
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
|
| 82 |
def _create_agents(self):
|
| 83 |
def make_agent(name, llm_override=None):
|
| 84 |
return Agent(
|
| 85 |
config=self.agents_config[name],
|
| 86 |
llm=llm_override or self.llm,
|
| 87 |
allow_delegation=False,
|
| 88 |
+
verbose=True,
|
| 89 |
max_iter=1,
|
| 90 |
+
respect_context_window=True,
|
| 91 |
+
# logs callbackagent
|
| 92 |
+
step_callback=lambda step: verbose_logger.info(f"Agent {name} Step: {step}"),
|
| 93 |
)
|
| 94 |
|
| 95 |
+
# Phase 2 : Agents d'extraction (existants)
|
| 96 |
+
self.cv_splitter = make_agent("cv_splitter", llm_override=self.big_llm)
|
| 97 |
+
self.skills_extractor = make_agent("skills_extractor")
|
| 98 |
+
self.experience_extractor = make_agent("experience_extractor")
|
| 99 |
+
self.project_extractor = make_agent("project_extractor")
|
| 100 |
+
self.education_extractor = make_agent("education_extractor")
|
| 101 |
+
self.reconversion_detector = make_agent("reconversion_detector")
|
| 102 |
+
self.language_extractor = make_agent("language_extractor")
|
| 103 |
+
self.etudiant_detector = make_agent("etudiant_detector")
|
| 104 |
+
self.identity_extractor = make_agent("identity_extractor")
|
| 105 |
+
|
| 106 |
+
# Phase 3 : Agents d'analyse et recommandation (nouveaux)
|
| 107 |
+
self.header_analyzer = make_agent("header_analyzer", llm_override=self.big_llm)
|
| 108 |
+
self.metier_matcher = make_agent("metier_matcher", llm_override=self.big_llm)
|
| 109 |
+
self.cv_quality_checker = make_agent("cv_quality_checker")
|
| 110 |
+
self.project_analyzer = make_agent("project_analyzer")
|
| 111 |
+
|
| 112 |
+
# ──────────────────────────────────────────────
|
| 113 |
+
# PHASE 1 : Découpage du CV en sections
|
| 114 |
+
# ──────────────────────────────────────────────
|
| 115 |
+
|
| 116 |
+
async def split_cv_sections(self, cv_content: str, cv_raw_start: str = "") -> Dict[str, str]:
|
| 117 |
+
"""Découpe le CV en sections via l'agent cv_splitter."""
|
| 118 |
+
task_config = self.tasks_config["split_cv_task"].copy()
|
| 119 |
+
# Échapper les accolades dans le contenu CV pour éviter les erreurs de format
|
| 120 |
+
safe_content = cv_content[:20000].replace("{", "{{").replace("}", "}}")
|
| 121 |
+
safe_raw = cv_raw_start[:2000].replace("{", "{{").replace("}", "}}")
|
| 122 |
+
task_config["description"] = task_config["description"].format(
|
| 123 |
+
cv_content=safe_content,
|
| 124 |
+
cv_raw_start=safe_raw,
|
| 125 |
)
|
| 126 |
+
|
| 127 |
+
task = Task(config=task_config, agent=self.cv_splitter)
|
| 128 |
crew = Crew(
|
| 129 |
agents=[self.cv_splitter],
|
| 130 |
tasks=[task],
|
| 131 |
process=Process.sequential,
|
| 132 |
+
verbose=False,
|
| 133 |
)
|
| 134 |
result = await crew.kickoff_async()
|
| 135 |
+
parsed = self._parse_json_output(result, default_structure={})
|
| 136 |
return parsed
|
| 137 |
|
| 138 |
+
# ──────────────────────────────────────────────
|
| 139 |
+
# PHASE 2 : Extraction parallèle (8 agents)
|
| 140 |
+
# ──────────────────────────────────────────────
|
| 141 |
+
|
| 142 |
+
async def extract_all_sections(
|
| 143 |
+
self, sections: Dict[str, str], cv_raw_start: str = "", file_name: str = ""
|
| 144 |
+
) -> Dict[str, Any]:
|
| 145 |
+
"""Exécute les 8 tâches d'extraction en parallèle."""
|
| 146 |
+
|
| 147 |
def create_task_async(task_key, agent, **kwargs):
|
| 148 |
t_config = self.tasks_config[task_key].copy()
|
| 149 |
+
t_description = t_config["description"]
|
| 150 |
+
|
| 151 |
+
# Éviter les erreurs de formattage si des clés manquent ou sont mal échappées (ex: accolades dans le texte du CV)
|
| 152 |
+
try:
|
| 153 |
+
# Utiliser format_map pour plus de flexibilité si besoin, mais format() est standard
|
| 154 |
+
t_config["description"] = t_description.format(**kwargs)
|
| 155 |
+
except KeyError as e:
|
| 156 |
+
logger.warning(f"KeyError formatting task '{task_key}': {e}. Falling back to manual replace.")
|
| 157 |
+
# Fallback manuel sécurisé pour les clés présentes
|
| 158 |
+
desc = t_description
|
| 159 |
+
for k, v in kwargs.items():
|
| 160 |
+
placeholder = "{" + k + "}"
|
| 161 |
+
if placeholder in desc:
|
| 162 |
+
desc = desc.replace(placeholder, str(v))
|
| 163 |
+
t_config["description"] = desc
|
| 164 |
+
except Exception as e:
|
| 165 |
+
logger.error(f"Unexpected error formatting task '{task_key}': {e}")
|
| 166 |
+
|
| 167 |
task = Task(config=t_config, agent=agent)
|
| 168 |
c = Crew(agents=[agent], tasks=[task], verbose=False)
|
| 169 |
return (task_key, c.kickoff_async())
|
| 170 |
|
| 171 |
tasks_def = [
|
| 172 |
+
(
|
| 173 |
+
"skills_task",
|
| 174 |
+
self.skills_extractor,
|
| 175 |
+
{
|
| 176 |
+
"experiences": sections.get("experiences", ""),
|
| 177 |
+
"projects": sections.get("projects", ""),
|
| 178 |
+
"skills": sections.get("skills", ""),
|
| 179 |
+
"education": sections.get("education", ""),
|
| 180 |
+
},
|
| 181 |
+
),
|
| 182 |
+
(
|
| 183 |
+
"experience_task",
|
| 184 |
+
self.experience_extractor,
|
| 185 |
+
{"experiences": sections.get("experiences", "")},
|
| 186 |
+
),
|
| 187 |
+
(
|
| 188 |
+
"project_task",
|
| 189 |
+
self.project_extractor,
|
| 190 |
+
{"projects": sections.get("projects", "")},
|
| 191 |
+
),
|
| 192 |
+
(
|
| 193 |
+
"education_task",
|
| 194 |
+
self.education_extractor,
|
| 195 |
+
{"education": sections.get("education", "")},
|
| 196 |
+
),
|
| 197 |
+
(
|
| 198 |
+
"reconversion_task",
|
| 199 |
+
self.reconversion_detector,
|
| 200 |
+
{
|
| 201 |
+
"experiences": sections.get("experiences", ""),
|
| 202 |
+
"education": sections.get("education", ""),
|
| 203 |
+
},
|
| 204 |
+
),
|
| 205 |
+
(
|
| 206 |
+
"language_task",
|
| 207 |
+
self.language_extractor,
|
| 208 |
+
{
|
| 209 |
+
"languages": sections.get("languages", ""),
|
| 210 |
+
"cv_raw_start": cv_raw_start[:500],
|
| 211 |
+
},
|
| 212 |
+
),
|
| 213 |
+
(
|
| 214 |
+
"etudiant_task",
|
| 215 |
+
self.etudiant_detector,
|
| 216 |
+
{
|
| 217 |
+
"education": sections.get("education", ""),
|
| 218 |
+
"current_date": datetime.now().strftime("%Y-%m-%d"),
|
| 219 |
+
},
|
| 220 |
+
),
|
| 221 |
+
(
|
| 222 |
+
"identity_task",
|
| 223 |
+
self.identity_extractor,
|
| 224 |
+
{
|
| 225 |
+
"header": sections.get("header", ""),
|
| 226 |
+
"cv_raw_start": cv_raw_start[:1500],
|
| 227 |
+
"file_name": file_name,
|
| 228 |
+
},
|
| 229 |
+
),
|
| 230 |
+
]
|
| 231 |
+
|
| 232 |
+
task_coroutines = [
|
| 233 |
+
create_task_async(key, agent, **kwargs) for key, agent, kwargs in tasks_def
|
| 234 |
]
|
|
|
|
| 235 |
keys = [t[0] for t in task_coroutines]
|
| 236 |
coroutines = [t[1] for t in task_coroutines]
|
| 237 |
results_list = await asyncio.gather(*coroutines, return_exceptions=True)
|
|
|
|
| 243 |
else:
|
| 244 |
results_map[key] = result
|
| 245 |
|
| 246 |
+
return self._aggregate_extraction_results(results_map)
|
| 247 |
+
|
| 248 |
+
# ──────────────────────────────────────────────
|
| 249 |
+
# PHASE 3 : Analyse & Recommandation (5 agents)
|
| 250 |
+
# ──────────────────────────────────────────────
|
| 251 |
+
|
| 252 |
+
async def analyze_and_recommend(
|
| 253 |
+
self,
|
| 254 |
+
cv_full_text: str,
|
| 255 |
+
sections: Dict[str, str],
|
| 256 |
+
extraction: Dict[str, Any],
|
| 257 |
+
cv_raw_start: str = "",
|
| 258 |
+
) -> Dict[str, Any]:
|
| 259 |
+
"""Exécute les 4 tâches d'analyse en 2 étapes optimisées.
|
| 260 |
+
|
| 261 |
+
Étape 3a : header_analyzer seul (rapide, nécessaire pour tous les autres)
|
| 262 |
+
Étape 3b : 3 agents en parallèle (quality, metier, project)
|
| 263 |
+
"""
|
| 264 |
+
|
| 265 |
+
candidat = extraction.get("candidat", {})
|
| 266 |
+
competences = candidat.get("compétences", {})
|
| 267 |
+
hard_skills = competences.get("hard_skills", [])
|
| 268 |
+
soft_skills = competences.get("soft_skills", [])
|
| 269 |
+
skills_with_context = competences.get("skills_with_context", [])
|
| 270 |
+
reconversion = candidat.get("reconversion", {})
|
| 271 |
+
|
| 272 |
+
# Identifier les domaines de compétences et méthodologies
|
| 273 |
+
skill_domains = self._map_skills_to_domains(hard_skills)
|
| 274 |
+
methodologies = self._extract_methodologies(hard_skills, skill_domains)
|
| 275 |
|
| 276 |
+
# Préparer les résumés pour les prompts
|
| 277 |
+
experiences_summary = json.dumps(
|
| 278 |
+
candidat.get("expériences", []), ensure_ascii=False
|
| 279 |
+
)[:3000]
|
| 280 |
+
projets = candidat.get("projets", {})
|
| 281 |
+
professional_projects = json.dumps(
|
| 282 |
+
projets.get("professional", []), ensure_ascii=False
|
| 283 |
+
)[:2000]
|
| 284 |
+
personal_projects = json.dumps(
|
| 285 |
+
projets.get("personal", []), ensure_ascii=False
|
| 286 |
+
)[:2000]
|
| 287 |
+
projects_summary = f"Pro: {professional_projects}\nPerso: {personal_projects}"
|
| 288 |
+
|
| 289 |
+
reconversion_data = json.dumps(reconversion, ensure_ascii=False) if reconversion else "{}"
|
| 290 |
+
|
| 291 |
+
# Préparer le référentiel métiers complet (30 métiers)
|
| 292 |
+
metiers_reference = self._prepare_metiers_for_prompt()
|
| 293 |
+
|
| 294 |
+
# Skills résumé pour header analysis (fallback)
|
| 295 |
+
skills_summary = ", ".join(hard_skills[:20]) if hard_skills else "Non identifiées"
|
| 296 |
+
|
| 297 |
+
def create_task_async(task_key, agent, **kwargs):
|
| 298 |
+
t_config = self.tasks_config[task_key].copy()
|
| 299 |
+
t_config["description"] = t_config["description"].format(**kwargs)
|
| 300 |
+
task = Task(config=t_config, agent=agent)
|
| 301 |
+
c = Crew(agents=[agent], tasks=[task], verbose=False)
|
| 302 |
+
return (task_key, c.kickoff_async())
|
| 303 |
+
|
| 304 |
+
# Utilise le texte brut fitz si fourni, sinon fallback sur le début du Markdown
|
| 305 |
+
raw_for_header = cv_raw_start[:2000] if cv_raw_start else cv_full_text[:2000]
|
| 306 |
+
header_section = sections.get("header", "")
|
| 307 |
+
safe_cv_raw = raw_for_header.replace("{", "{{").replace("}", "}}")
|
| 308 |
+
safe_header = header_section.replace("{", "{{").replace("}", "}}")
|
| 309 |
+
safe_skills = skills_summary.replace("{", "{{").replace("}", "}}")
|
| 310 |
+
header_data = {
|
| 311 |
+
"poste_vise": "Non identifié",
|
| 312 |
+
"niveau_seniorite": "non précisé",
|
| 313 |
+
"confiance": 0,
|
| 314 |
+
}
|
| 315 |
+
|
| 316 |
+
try:
|
| 317 |
+
header_coroutine = create_task_async(
|
| 318 |
+
"poste_visé_task",
|
| 319 |
+
self.header_analyzer,
|
| 320 |
+
cv_raw_start=safe_cv_raw,
|
| 321 |
+
header=safe_header,
|
| 322 |
+
skills_summary=safe_skills,
|
| 323 |
+
)
|
| 324 |
+
header_result = await header_coroutine[1]
|
| 325 |
+
|
| 326 |
+
if header_result:
|
| 327 |
+
header_data = self._parse_json_output(
|
| 328 |
+
header_result,
|
| 329 |
+
{
|
| 330 |
+
"poste_vise": "Non identifié",
|
| 331 |
+
"niveau_seniorite": "non précisé",
|
| 332 |
+
"confiance": 0,
|
| 333 |
+
},
|
| 334 |
+
)
|
| 335 |
+
logger.info(f"Header analyzer result: poste_vise='{header_data.get('poste_vise')}', confiance={header_data.get('confiance')}")
|
| 336 |
+
except Exception as e:
|
| 337 |
+
logger.error(f"Header analyzer failed: {e}", exc_info=True)
|
| 338 |
+
|
| 339 |
+
poste_vise = header_data.get("poste_vise", "Non identifié")
|
| 340 |
+
niveau_seniorite = header_data.get("niveau_seniorite", "non précisé")
|
| 341 |
+
|
| 342 |
+
# --- Fallback programmatique si le LLM n'a pas trouvé le poste ---
|
| 343 |
+
if poste_vise == "Non identifié":
|
| 344 |
+
logger.warning("Header analyzer returned 'Non identifié', trying fallback extraction...")
|
| 345 |
+
fallback = self._fallback_extract_poste_vise(
|
| 346 |
+
cv_full_text, header_section
|
| 347 |
+
)
|
| 348 |
+
if fallback:
|
| 349 |
+
poste_vise = fallback
|
| 350 |
+
header_data["poste_vise"] = fallback
|
| 351 |
+
header_data["source_detection"] = "fallback_programmatique"
|
| 352 |
+
header_data["confiance"] = 70
|
| 353 |
+
logger.info(f"Fallback found poste_vise: '{fallback}'")
|
| 354 |
+
|
| 355 |
+
# Préparer le détail du métier pour le project_analyzer
|
| 356 |
+
metier_reference_detail = self._get_metier_reference_for_poste(poste_vise)
|
| 357 |
+
|
| 358 |
+
# --- Étape 3b : 3 agents en parallèle ---
|
| 359 |
+
parallel_tasks = [
|
| 360 |
+
(
|
| 361 |
+
"cv_quality_task",
|
| 362 |
+
self.cv_quality_checker,
|
| 363 |
+
{
|
| 364 |
+
"cv_full_text": cv_full_text[:8000],
|
| 365 |
+
"cv_raw_start": safe_cv_raw,
|
| 366 |
+
"skills_with_context": json.dumps(
|
| 367 |
+
skills_with_context, ensure_ascii=False
|
| 368 |
+
)[:2000],
|
| 369 |
+
"experiences_summary": experiences_summary,
|
| 370 |
+
"projects_summary": projects_summary[:2000],
|
| 371 |
+
"niveau_seniorite": niveau_seniorite,
|
| 372 |
+
"reconversion_data": reconversion_data,
|
| 373 |
+
},
|
| 374 |
+
),
|
| 375 |
+
(
|
| 376 |
+
"metier_matching_task",
|
| 377 |
+
self.metier_matcher,
|
| 378 |
+
{
|
| 379 |
+
"poste_vise": poste_vise,
|
| 380 |
+
"hard_skills": json.dumps(hard_skills, ensure_ascii=False),
|
| 381 |
+
"soft_skills": json.dumps(soft_skills, ensure_ascii=False),
|
| 382 |
+
"skill_domains": json.dumps(skill_domains, ensure_ascii=False),
|
| 383 |
+
"methodologies": json.dumps(methodologies, ensure_ascii=False),
|
| 384 |
+
"experiences_summary": experiences_summary,
|
| 385 |
+
"projects_summary": projects_summary[:2000],
|
| 386 |
+
"reconversion_data": reconversion_data,
|
| 387 |
+
"metiers_reference": metiers_reference,
|
| 388 |
+
},
|
| 389 |
+
),
|
| 390 |
+
(
|
| 391 |
+
"project_analysis_task",
|
| 392 |
+
self.project_analyzer,
|
| 393 |
+
{
|
| 394 |
+
"poste_vise": poste_vise,
|
| 395 |
+
"metier_reference_detail": metier_reference_detail,
|
| 396 |
+
"experiences_summary": experiences_summary,
|
| 397 |
+
"professional_projects": professional_projects,
|
| 398 |
+
"personal_projects": personal_projects,
|
| 399 |
+
"reconversion_data": reconversion_data,
|
| 400 |
+
},
|
| 401 |
+
),
|
| 402 |
+
]
|
| 403 |
+
|
| 404 |
+
task_coroutines = [
|
| 405 |
+
create_task_async(key, agent, **kwargs) for key, agent, kwargs in parallel_tasks
|
| 406 |
+
]
|
| 407 |
+
keys = [t[0] for t in task_coroutines]
|
| 408 |
+
coroutines = [t[1] for t in task_coroutines]
|
| 409 |
+
results_list = await asyncio.gather(*coroutines, return_exceptions=True)
|
| 410 |
+
|
| 411 |
+
analysis_results = {}
|
| 412 |
+
for key, result in zip(keys, results_list):
|
| 413 |
+
if isinstance(result, Exception):
|
| 414 |
+
logger.error(f"Analysis task '{key}' failed: {result}")
|
| 415 |
+
else:
|
| 416 |
+
analysis_results[key] = result
|
| 417 |
+
|
| 418 |
+
return self._aggregate_recommendations(
|
| 419 |
+
analysis_results,
|
| 420 |
+
header_data,
|
| 421 |
+
poste_vise,
|
| 422 |
+
)
|
| 423 |
+
|
| 424 |
+
# ──────────────────────────────────────────────
|
| 425 |
+
# Mapping compétences -> domaines
|
| 426 |
+
# ──────────────────────────────────────────────
|
| 427 |
+
|
| 428 |
+
def _map_skills_to_domains(self, hard_skills: List[str]) -> Dict[str, List[str]]:
|
| 429 |
+
"""Mappe les compétences du candidat à leurs domaines métier."""
|
| 430 |
+
result = {}
|
| 431 |
+
for skill in hard_skills:
|
| 432 |
+
skill_lower = skill.lower().strip()
|
| 433 |
+
for domain, domain_skills in self.skill_domain_map.items():
|
| 434 |
+
if skill_lower in domain_skills:
|
| 435 |
+
if domain not in result:
|
| 436 |
+
result[domain] = []
|
| 437 |
+
result[domain].append(skill)
|
| 438 |
+
break
|
| 439 |
+
return result
|
| 440 |
+
|
| 441 |
+
def _prepare_metiers_for_prompt(self) -> str:
|
| 442 |
+
"""Prépare le référentiel métiers COMPLET (30 métiers) pour le prompt."""
|
| 443 |
+
lines = []
|
| 444 |
+
for m in self.metiers_data:
|
| 445 |
+
mid = m.get("id", "?")
|
| 446 |
+
nom = m.get("nom", "?")
|
| 447 |
+
cat = m.get("categorie", "?")
|
| 448 |
+
comp = m.get("competences_techniques", [])
|
| 449 |
+
outils = m.get("outils_technologies", [])
|
| 450 |
+
soft = m.get("competences_soft", [])
|
| 451 |
+
niveau = m.get("niveau_etude", "?")
|
| 452 |
+
exp = m.get("experience_requise", "?")
|
| 453 |
+
lines.append(
|
| 454 |
+
f"[{mid}] {nom} ({cat})\n"
|
| 455 |
+
f" Compétences techniques: {', '.join(comp)}\n"
|
| 456 |
+
f" Outils: {', '.join(outils)}\n"
|
| 457 |
+
f" Soft skills: {', '.join(soft[:3])}\n"
|
| 458 |
+
f" Niveau: {niveau} | Expérience: {exp}"
|
| 459 |
+
)
|
| 460 |
+
return "\n\n".join(lines)
|
| 461 |
+
|
| 462 |
+
def _get_metier_reference_for_poste(self, poste_vise: str) -> str:
|
| 463 |
+
"""Trouve les métiers les plus proches du poste visé pour contextualiser l'analyse de projets."""
|
| 464 |
+
if not poste_vise or poste_vise == "Non identifié":
|
| 465 |
+
return "Aucun métier de référence spécifique. Analyser les projets selon leur qualité intrinsèque."
|
| 466 |
+
|
| 467 |
+
poste_lower = poste_vise.lower()
|
| 468 |
+
scored = []
|
| 469 |
+
|
| 470 |
+
for m in self.metiers_data:
|
| 471 |
+
nom_lower = m.get("nom", "").lower()
|
| 472 |
+
id_lower = m.get("id", "").lower()
|
| 473 |
+
desc_lower = m.get("description", "").lower()
|
| 474 |
+
score = 0
|
| 475 |
+
|
| 476 |
+
keywords = [w for w in poste_lower.replace("/", " ").replace("-", " ").split() if len(w) > 2]
|
| 477 |
+
for kw in keywords:
|
| 478 |
+
if kw in nom_lower:
|
| 479 |
+
score += 3
|
| 480 |
+
if kw in id_lower:
|
| 481 |
+
score += 2
|
| 482 |
+
if kw in desc_lower:
|
| 483 |
+
score += 1
|
| 484 |
+
|
| 485 |
+
nom_keywords = [w for w in nom_lower.replace("/", " ").replace("-", " ").split() if len(w) > 2]
|
| 486 |
+
for kw in nom_keywords:
|
| 487 |
+
if kw in poste_lower:
|
| 488 |
+
score += 3
|
| 489 |
+
|
| 490 |
+
if score > 0:
|
| 491 |
+
scored.append((score, m))
|
| 492 |
+
|
| 493 |
+
scored.sort(key=lambda x: -x[0])
|
| 494 |
+
|
| 495 |
+
if not scored:
|
| 496 |
+
return "Poste visé non trouvé dans le référentiel. Analyser les projets selon leur qualité intrinsèque."
|
| 497 |
+
|
| 498 |
+
lines = ["Métier(s) de référence les plus proches du poste visé :"]
|
| 499 |
+
for _, m in scored[:3]:
|
| 500 |
+
mid = m.get("id")
|
| 501 |
+
nom = m.get("nom")
|
| 502 |
+
comp = m.get("competences_techniques", [])
|
| 503 |
+
outils = m.get("outils_technologies", [])
|
| 504 |
+
missions = m.get("missions_principales", [])
|
| 505 |
+
lines.append(
|
| 506 |
+
f"\n[{mid}] {nom}\n"
|
| 507 |
+
f" Compétences attendues: {', '.join(comp)}\n"
|
| 508 |
+
f" Outils attendus: {', '.join(outils)}\n"
|
| 509 |
+
f" Missions principales: {'; '.join(missions[:3])}"
|
| 510 |
+
)
|
| 511 |
+
return "\n".join(lines)
|
| 512 |
+
|
| 513 |
+
def _extract_methodologies(self, hard_skills: List[str], skill_domains: Dict[str, List[str]]) -> List[str]:
|
| 514 |
+
"""Extrait les méthodologies de travail du candidat."""
|
| 515 |
+
methodology_keywords = {
|
| 516 |
+
"agile", "scrum", "kanban", "devops", "ci/cd", "cicd", "tdd", "bdd",
|
| 517 |
+
"design thinking", "lean", "safe", "xp", "pair programming",
|
| 518 |
+
"code review", "sprint", "product owner", "scrum master",
|
| 519 |
+
"rgpd", "rgaa",
|
| 520 |
+
}
|
| 521 |
+
|
| 522 |
+
methodologies = []
|
| 523 |
+
for skill in hard_skills:
|
| 524 |
+
if skill.lower().strip() in methodology_keywords:
|
| 525 |
+
methodologies.append(skill)
|
| 526 |
+
|
| 527 |
+
if "gestion_projet" in skill_domains:
|
| 528 |
+
for skill in skill_domains["gestion_projet"]:
|
| 529 |
+
if skill not in methodologies:
|
| 530 |
+
methodologies.append(skill)
|
| 531 |
+
|
| 532 |
+
if "devops" in skill_domains:
|
| 533 |
+
for skill in skill_domains["devops"]:
|
| 534 |
+
s = skill.lower()
|
| 535 |
+
if any(kw in s for kw in ["ci", "cd", "github actions", "gitlab ci"]):
|
| 536 |
+
if skill not in methodologies:
|
| 537 |
+
methodologies.append(skill)
|
| 538 |
+
|
| 539 |
+
return methodologies
|
| 540 |
+
|
| 541 |
+
# ──────────────────────────────────────────────
|
| 542 |
+
# Agrégation des résultats d'extraction (Phase 2)
|
| 543 |
+
# ──────────────────────────────────────────────
|
| 544 |
+
|
| 545 |
+
def _aggregate_extraction_results(self, results_map: Dict[str, Any]) -> Dict[str, Any]:
|
| 546 |
+
"""Agrège les résultats d'extraction (identique au module existant)."""
|
| 547 |
|
| 548 |
def get_parsed(key, default=None):
|
| 549 |
if key not in results_map:
|
| 550 |
return default
|
| 551 |
return self._parse_json_output(results_map[key], default)
|
| 552 |
|
| 553 |
+
competences = get_parsed("skills_task", {"hard_skills": [], "soft_skills": []})
|
| 554 |
+
experiences = get_parsed("experience_task", [])
|
| 555 |
+
projets = get_parsed("project_task", {"professional": [], "personal": []})
|
| 556 |
+
formations = get_parsed("education_task", [])
|
| 557 |
+
reconversion = get_parsed("reconversion_task", {}).get(
|
| 558 |
+
"reconversion_analysis", {}
|
| 559 |
+
)
|
| 560 |
+
etudiant_data = get_parsed("etudiant_task", {}).get("etudiant_analysis", {})
|
| 561 |
latest_end_date = etudiant_data.get("latest_education_end_date")
|
| 562 |
if latest_end_date:
|
| 563 |
is_student_by_date = self._is_still_student(latest_end_date)
|
| 564 |
etudiant_data["is_etudiant"] = is_student_by_date
|
| 565 |
|
| 566 |
+
langues_raw = get_parsed("language_task", {})
|
| 567 |
|
| 568 |
if isinstance(competences, dict):
|
|
|
|
| 569 |
raw_skills = competences.get("hard_skills", [])
|
| 570 |
seen = set()
|
| 571 |
unique_skills = []
|
| 572 |
for skill in raw_skills:
|
| 573 |
+
key = (
|
| 574 |
+
str(skill).lower()
|
| 575 |
+
if not isinstance(skill, str)
|
| 576 |
+
else skill.lower()
|
| 577 |
+
)
|
| 578 |
if key not in seen:
|
| 579 |
seen.add(key)
|
| 580 |
unique_skills.append(skill)
|
| 581 |
competences["hard_skills"] = unique_skills
|
| 582 |
|
| 583 |
+
identity = get_parsed("identity_task", {})
|
| 584 |
|
| 585 |
return {
|
| 586 |
"candidat": {
|
| 587 |
+
"first_name": (
|
| 588 |
+
identity.get("first_name")
|
| 589 |
+
if isinstance(identity, dict)
|
| 590 |
+
else None
|
| 591 |
+
),
|
| 592 |
"compétences": competences,
|
| 593 |
"expériences": experiences,
|
| 594 |
"reconversion": reconversion,
|
| 595 |
"projets": projets,
|
| 596 |
"formations": formations,
|
| 597 |
"etudiant": etudiant_data,
|
| 598 |
+
"langues": (
|
| 599 |
+
langues_raw.get("langues", [])
|
| 600 |
+
if isinstance(langues_raw, dict)
|
| 601 |
+
else []
|
| 602 |
+
),
|
| 603 |
}
|
| 604 |
}
|
| 605 |
|
| 606 |
+
# ──────────────────────────────────────────────
|
| 607 |
+
# Agrégation des recommandations (Phase 3)
|
| 608 |
+
# ──────────────────────────────────────────────
|
| 609 |
+
|
| 610 |
+
def _aggregate_recommendations(
|
| 611 |
+
self,
|
| 612 |
+
analysis_results: Dict[str, Any],
|
| 613 |
+
header_data: Dict,
|
| 614 |
+
poste_vise: str,
|
| 615 |
+
) -> Dict[str, Any]:
|
| 616 |
+
"""Agrège les résultats d'analyse avec des recommandations orientées projets."""
|
| 617 |
+
|
| 618 |
+
def get_parsed(key, default=None):
|
| 619 |
+
if key not in analysis_results:
|
| 620 |
+
return default
|
| 621 |
+
return self._parse_json_output(analysis_results[key], default)
|
| 622 |
+
|
| 623 |
+
metier_data = get_parsed("metier_matching_task", {"postes_recommandes": []})
|
| 624 |
+
quality_data = get_parsed(
|
| 625 |
+
"cv_quality_task",
|
| 626 |
+
{"score_global": 0, "red_flags": [], "conseils_prioritaires": []},
|
| 627 |
+
)
|
| 628 |
+
project_data = get_parsed("project_analysis_task", {"analyse_projets": []})
|
| 629 |
+
|
| 630 |
+
# ── Conseils d'amélioration ────────────────────────────────────────────
|
| 631 |
+
conseils = []
|
| 632 |
+
|
| 633 |
+
# 1. Conseils qualité CV
|
| 634 |
+
if isinstance(quality_data, dict):
|
| 635 |
+
conseils.extend(quality_data.get("conseils_prioritaires", []))
|
| 636 |
+
|
| 637 |
+
# 2. Projets à mettre en avant
|
| 638 |
+
if isinstance(project_data, dict):
|
| 639 |
+
for item in (project_data.get("ordre_mise_en_avant", []) or [])[:3]:
|
| 640 |
+
if isinstance(item, dict) and item.get("raison"):
|
| 641 |
+
conseils.append(
|
| 642 |
+
f"Projet prioritaire #{item.get('rang', '?')} à mettre en avant"
|
| 643 |
+
f" - '{item.get('titre', '?')}' : {item['raison']}"
|
| 644 |
+
)
|
| 645 |
+
|
| 646 |
+
return {
|
| 647 |
+
"header_analysis": header_data,
|
| 648 |
+
"postes_recommandes": (
|
| 649 |
+
metier_data.get("postes_recommandes", [])
|
| 650 |
+
if isinstance(metier_data, dict)
|
| 651 |
+
else []
|
| 652 |
+
),
|
| 653 |
+
"analyse_poste_vise": (
|
| 654 |
+
metier_data.get("analyse_poste_vise", "")
|
| 655 |
+
if isinstance(metier_data, dict)
|
| 656 |
+
else ""
|
| 657 |
+
),
|
| 658 |
+
"qualite_cv": quality_data,
|
| 659 |
+
"analyse_projets": (
|
| 660 |
+
project_data.get("analyse_projets", [])
|
| 661 |
+
if isinstance(project_data, dict)
|
| 662 |
+
else []
|
| 663 |
+
),
|
| 664 |
+
"ordre_mise_en_avant_projets": (
|
| 665 |
+
project_data.get("ordre_mise_en_avant", [])
|
| 666 |
+
if isinstance(project_data, dict)
|
| 667 |
+
else []
|
| 668 |
+
),
|
| 669 |
+
"coherence_globale_projets": (
|
| 670 |
+
project_data.get("coherence_globale", {})
|
| 671 |
+
if isinstance(project_data, dict)
|
| 672 |
+
else {}
|
| 673 |
+
),
|
| 674 |
+
"conseils_amelioration": conseils,
|
| 675 |
+
}
|
| 676 |
+
|
| 677 |
+
# ──────────────────────────────────────────────
|
| 678 |
+
# Utilitaires
|
| 679 |
+
# ──────────────────────────────────────────────
|
| 680 |
+
|
| 681 |
+
def _fallback_extract_poste_vise(
|
| 682 |
+
self, cv_full_text: str, header_section: str
|
| 683 |
+
) -> str:
|
| 684 |
+
"""Extraction programmatique du poste visé en fallback.
|
| 685 |
+
|
| 686 |
+
Cherche la ligne de titre dans l'en-tête du CV en filtrant les lignes
|
| 687 |
+
qui ne sont clairement PAS un titre de poste (email, téléphone, liens,
|
| 688 |
+
titres de section, compétences techniques).
|
| 689 |
+
"""
|
| 690 |
+
import re
|
| 691 |
+
|
| 692 |
+
# Patterns qui NE sont PAS un titre de poste
|
| 693 |
+
skip_patterns = [
|
| 694 |
+
r"^#{1,6}\s", # Titres markdown
|
| 695 |
+
r"@", # Email
|
| 696 |
+
r"^\+?\d[\d\s\-\.]{7,}", # Téléphone
|
| 697 |
+
r"^http|^www\.|linkedin|github", # URLs/liens
|
| 698 |
+
r"^\*{1,3}[A-Z]", # Bold section headers
|
| 699 |
+
r"^(CONTACT|LIENS|STACK|LANGUES|CENTRES|EXPERIENCE|FORMATION|PROJET|COMPÉTENCES|EDUCATION)", # Section headings
|
| 700 |
+
r"^(Python|SQL|JavaScript|React|FastAPI|Docker|AWS|Git|CI)", # Skills
|
| 701 |
+
r"^(Ile-de-France|Paris|Lyon|Marseille|France)", # Locations
|
| 702 |
+
r"^\d{2}\s?\d{2}\s?\d{2}", # Phone numbers
|
| 703 |
+
r"^(Français|Anglais|Portugais|Espagnol)", # Languages
|
| 704 |
+
r"^(Langages|Frameworks|Analytics|DevOps|Méthodologies|IA &|BI :)", # Skill categories
|
| 705 |
+
r"^(Blockchain|Jeux de rôle|Randonnée)", # Interests
|
| 706 |
+
r"^\s*$", # Empty lines
|
| 707 |
+
r"^[\*\-\|]", # List items and table separators
|
| 708 |
+
]
|
| 709 |
+
|
| 710 |
+
# Mots-clés qui INDIQUENT un titre de poste
|
| 711 |
+
title_indicators = [
|
| 712 |
+
"développeur", "developer", "ingénieur", "engineer", "chef de projet",
|
| 713 |
+
"data analyst", "data scientist", "data engineer", "consultant",
|
| 714 |
+
"architecte", "manager", "lead", "senior", "junior", "fullstack",
|
| 715 |
+
"full-stack", "full stack", "backend", "frontend", "devops",
|
| 716 |
+
"product", "project", "spécialiste", "expert", "analyste",
|
| 717 |
+
"mlops", "ai", "ia", "machine learning", "nlp", "deep learning",
|
| 718 |
+
]
|
| 719 |
+
|
| 720 |
+
def _has_title_indicator(text_lower: str) -> bool:
|
| 721 |
+
for indicator in title_indicators:
|
| 722 |
+
if len(indicator) <= 3:
|
| 723 |
+
if re.search(r"\b" + re.escape(indicator) + r"\b", text_lower):
|
| 724 |
+
return True
|
| 725 |
+
else:
|
| 726 |
+
if indicator in text_lower:
|
| 727 |
+
return True
|
| 728 |
+
return False
|
| 729 |
+
|
| 730 |
+
def _is_likely_title(line: str) -> bool:
|
| 731 |
+
stripped = line.strip().strip("#*_ ")
|
| 732 |
+
if len(line.split()) > 10:
|
| 733 |
+
return False
|
| 734 |
+
for pattern in skip_patterns:
|
| 735 |
+
if re.match(pattern, stripped, re.IGNORECASE):
|
| 736 |
+
return False
|
| 737 |
+
return _has_title_indicator(stripped.lower())
|
| 738 |
+
|
| 739 |
+
# Chercher dans toutes les sources, par ordre de priorité
|
| 740 |
+
sources = [
|
| 741 |
+
("header", header_section),
|
| 742 |
+
("cv_text", cv_full_text[:3000]),
|
| 743 |
+
]
|
| 744 |
+
|
| 745 |
+
for source_name, text in sources:
|
| 746 |
+
if not text:
|
| 747 |
+
continue
|
| 748 |
+
lines = text.split("\n")
|
| 749 |
+
for line in lines:
|
| 750 |
+
if _is_likely_title(line):
|
| 751 |
+
clean = line.strip().strip("#*_ ")
|
| 752 |
+
logger.info(f"Fallback: found title in {source_name}: '{clean}'")
|
| 753 |
+
return clean
|
| 754 |
+
|
| 755 |
+
return ""
|
| 756 |
+
|
| 757 |
def _is_still_student(self, date_str: str) -> bool:
|
| 758 |
+
"""Détermine si le candidat est encore étudiant à partir de la date de fin d'études."""
|
| 759 |
if not date_str:
|
| 760 |
return False
|
| 761 |
date_str = str(date_str).lower().strip()
|
| 762 |
+
ongoing_keywords = [
|
| 763 |
+
"present", "présent", "current", "cours", "aujourd'hui", "now"
|
| 764 |
+
]
|
| 765 |
if any(keyword in date_str for keyword in ongoing_keywords):
|
| 766 |
return True
|
| 767 |
+
|
| 768 |
try:
|
| 769 |
now = datetime.now()
|
| 770 |
end_date = None
|
| 771 |
+
if len(date_str) == 10 and date_str[4] == "-" and date_str[7] == "-":
|
| 772 |
+
end_date = datetime.strptime(date_str, "%Y-%m-%d")
|
| 773 |
+
elif len(date_str) == 7 and date_str[4] == "-":
|
| 774 |
+
end_date = datetime.strptime(date_str, "%Y-%m")
|
| 775 |
+
elif "/" in date_str:
|
| 776 |
+
parts = date_str.split("/")
|
| 777 |
if len(parts) == 2:
|
| 778 |
+
_, y = parts
|
| 779 |
if len(y) == 4:
|
| 780 |
+
end_date = datetime.strptime(date_str, "%m/%Y")
|
| 781 |
elif len(y) == 2:
|
| 782 |
+
end_date = datetime.strptime(date_str, "%m/%y")
|
|
|
|
| 783 |
elif len(date_str) == 4 and date_str.isdigit():
|
| 784 |
+
end_date = datetime.strptime(date_str, "%Y")
|
| 785 |
+
end_date = end_date.replace(month=12, day=31)
|
| 786 |
+
|
| 787 |
if end_date:
|
| 788 |
return end_date >= now
|
|
|
|
| 789 |
return False
|
|
|
|
| 790 |
except (ValueError, IndexError):
|
| 791 |
logger.warning(f"Date parsing failed for: {date_str}")
|
| 792 |
return False
|
| 793 |
|
| 794 |
def _parse_json_output(self, crew_output, default_structure=None) -> Any:
|
| 795 |
+
"""Parse la sortie JSON d'un agent CrewAI avec nettoyage robuste."""
|
| 796 |
+
if crew_output is None:
|
| 797 |
+
return default_structure if default_structure is not None else {}
|
| 798 |
+
|
| 799 |
+
raw = crew_output.raw if hasattr(crew_output, "raw") else str(crew_output)
|
| 800 |
|
| 801 |
+
# Extraire le bloc JSON si encapsulé dans des backticks
|
| 802 |
+
if "```json" in raw:
|
| 803 |
+
raw = raw.split("```json")[1].split("```")[0].strip()
|
| 804 |
+
elif "```" in raw:
|
| 805 |
+
parts = raw.split("```")
|
| 806 |
if len(parts) >= 3:
|
| 807 |
raw = parts[1].strip()
|
| 808 |
|
| 809 |
+
raw = raw.strip().lstrip("\ufeff")
|
|
|
|
| 810 |
|
| 811 |
+
def _try_parse(text: str):
|
| 812 |
+
"""Tente un parse direct puis un parse avec extraction du premier bloc JSON."""
|
| 813 |
+
try:
|
| 814 |
+
return json.loads(text)
|
| 815 |
+
except json.JSONDecodeError:
|
| 816 |
+
pass
|
| 817 |
+
for start_char, end_char in [("{", "}"), ("[", "]")]:
|
| 818 |
+
start_idx = text.find(start_char)
|
| 819 |
+
end_idx = text.rfind(end_char)
|
| 820 |
if start_idx != -1 and end_idx > start_idx:
|
| 821 |
try:
|
| 822 |
+
return json.loads(text[start_idx : end_idx + 1])
|
| 823 |
except json.JSONDecodeError:
|
| 824 |
+
pass
|
| 825 |
+
return None
|
| 826 |
+
|
| 827 |
+
# Tentative 1 : parse du texte tel quel (gère "JSON : {...}" et JSON propre)
|
| 828 |
+
result = _try_parse(raw)
|
| 829 |
+
if result is not None:
|
| 830 |
+
return result
|
| 831 |
+
|
| 832 |
+
# Tentative 2 : le LLM a copié les {{ }} du expected_output YAML.
|
| 833 |
+
# ⚠️ On ne remplace QUE si {{ est détecté — évite de casser un JSON
|
| 834 |
+
# compact valide du type {"inner": {"key": "val"}} → {"inner": {"key": "val"}
|
| 835 |
+
if "{{" in raw:
|
| 836 |
+
cleaned = raw.replace("{{", "{").replace("}}", "}")
|
| 837 |
+
result = _try_parse(cleaned)
|
| 838 |
+
if result is not None:
|
| 839 |
+
return result
|
| 840 |
|
| 841 |
+
logger.error(f"JSON Parse Error (after cleanup): {raw[:200]}")
|
| 842 |
+
return default_structure if default_structure is not None else {}
|
src/services/cv_service.py
CHANGED
|
@@ -1,14 +1,70 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import logging
|
| 2 |
from typing import Dict, Any
|
| 3 |
-
|
|
|
|
| 4 |
from src.parser_flow.CV_agent_flow import CVAgentOrchestrator
|
| 5 |
|
| 6 |
logger = logging.getLogger(__name__)
|
| 7 |
|
| 8 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
orchestrator = CVAgentOrchestrator()
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
cv_text = load_pdf(pdf_path)
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Service de parsing et analyse de CV enrichi.
|
| 3 |
+
Pipeline en 3 phases :
|
| 4 |
+
1. Découpage en sections (avec extraction brute pour le header)
|
| 5 |
+
2. Extraction parallèle (compétences, expériences, projets, etc.)
|
| 6 |
+
3. Analyse et recommandation (poste visé, matching métiers, qualité CV, projets)
|
| 7 |
+
"""
|
| 8 |
+
|
| 9 |
import logging
|
| 10 |
from typing import Dict, Any
|
| 11 |
+
|
| 12 |
+
from src.config.app_config import load_pdf, load_pdf_first_page_text
|
| 13 |
from src.parser_flow.CV_agent_flow import CVAgentOrchestrator
|
| 14 |
|
| 15 |
logger = logging.getLogger(__name__)
|
| 16 |
|
| 17 |
+
|
| 18 |
+
async def parse_cv_enriched(pdf_path: str, file_name: str = "") -> Dict[str, Any]:
|
| 19 |
+
"""
|
| 20 |
+
Parse un CV avec extraction + analyse complète.
|
| 21 |
+
|
| 22 |
+
Retourne un JSON en 2 parties :
|
| 23 |
+
- candidat : données parsées du CV (identité, compétences, expériences, projets…)
|
| 24 |
+
- recommandations : analyse critique, matching métiers, qualité CV, header_analysis (poste_vise)
|
| 25 |
+
"""
|
| 26 |
orchestrator = CVAgentOrchestrator()
|
| 27 |
+
|
| 28 |
+
# Double extraction :
|
| 29 |
+
# - cv_text : Markdown (bon pour la structure des sections)
|
| 30 |
+
# - cv_raw_start : texte brut ordonné par position (fiable pour le header/nom/titre)
|
| 31 |
cv_text = load_pdf(pdf_path)
|
| 32 |
+
cv_raw_start = load_pdf_first_page_text(pdf_path)
|
| 33 |
+
|
| 34 |
+
logger.info("Phase 1 : Découpage du CV en sections...")
|
| 35 |
+
sections = await orchestrator.split_cv_sections(cv_text, cv_raw_start=cv_raw_start)
|
| 36 |
+
|
| 37 |
+
logger.info("Phase 2 : Extraction parallèle des données...")
|
| 38 |
+
extraction = await orchestrator.extract_all_sections(
|
| 39 |
+
sections, cv_raw_start=cv_raw_start, file_name=file_name
|
| 40 |
+
)
|
| 41 |
+
|
| 42 |
+
logger.info("Phase 3 : Analyse et recommandation...")
|
| 43 |
+
recommendations = await orchestrator.analyze_and_recommend(
|
| 44 |
+
cv_full_text=cv_text,
|
| 45 |
+
sections=sections,
|
| 46 |
+
extraction=extraction,
|
| 47 |
+
cv_raw_start=cv_raw_start,
|
| 48 |
+
)
|
| 49 |
+
|
| 50 |
+
candidat_raw = extraction.get("candidat", {})
|
| 51 |
+
|
| 52 |
+
# Assemblage ordonné : identité → langues → compétences → parcours
|
| 53 |
+
candidat = {
|
| 54 |
+
"first_name": candidat_raw.get("first_name"),
|
| 55 |
+
"langues": candidat_raw.get("langues", []),
|
| 56 |
+
"compétences": candidat_raw.get("compétences", {}),
|
| 57 |
+
"expériences": candidat_raw.get("expériences", []),
|
| 58 |
+
"projets": candidat_raw.get("projets", {}),
|
| 59 |
+
"formations": candidat_raw.get("formations", []),
|
| 60 |
+
"etudiant": candidat_raw.get("etudiant", {}),
|
| 61 |
+
"reconversion": candidat_raw.get("reconversion", {}),
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
result = {
|
| 65 |
+
"candidat": candidat,
|
| 66 |
+
"recommandations": recommendations,
|
| 67 |
+
}
|
| 68 |
|
| 69 |
+
logger.info("Parsing et analyse terminés.")
|
| 70 |
+
return result
|