Ce que vous saurez faire à la fin
- Scorer automatiquement 1 000 CV en moins de 20 minutes avec Claude API
- Concevoir une grille pondérée alignée avec votre fiche de poste
- Générer un rapport CSV avec top 50 candidats + justifications détaillées
- Éviter les biais de genre, âge, origine par anonymisation pré-traitement
- Calculer le ROI : économiser 40 à 60 heures de pré-sélection RH par campagne
Prérequis
- Compte Anthropic avec clé API et au moins 20 USD de crédit
- Python 3.10+ installé, pip, virtualenv
- Un dossier avec 1 000 CV en PDF (ou URL drive partagé)
- Une fiche de poste clairement définie (titre, compétences must/nice, expérience)
Étape 1 — Construire la grille de scoring pondérée
Sans grille, Claude note au pif. Définissez 5 à 7 critères avec un poids total de 100.
Critère Poids Échelle
Expérience domaine 30 0-10
Compétences techniques 25 0-10
Langues (FR/EN/Wolof) 10 0-10
Diplôme 10 0-10
Réalisations quantifiées 15 0-10
Adéquation géographique 10 0-10
Étape 2 — Anonymiser les CV pour éviter les biais
import re
def anonymiser(texte: str) -> str:
# Nom (1ère ligne souvent), email, téléphone, date naissance, photo
texte = re.sub(r'\b[A-Za-z.]+@[A-Za-z.-]+\.\w+', '[EMAIL]', texte)
texte = re.sub(r'\+?\d{2,3}[\s-]?\d{2,3}[\s-]?\d{2,3}[\s-]?\d{2,4}', '[TEL]', texte)
texte = re.sub(r'\b\d{1,2}[/\.-]\d{1,2}[/\.-]\d{2,4}\b', '[DATE]', texte)
# Première ligne probablement nom
lignes = texte.split('\n')
if len(lignes) > 0:
lignes[0] = '[NOM]'
return '\n'.join(lignes)
Étape 3 — Extraire le texte des CV PDF
import pypdf, os
def cv_en_texte(chemin_pdf):
reader = pypdf.PdfReader(chemin_pdf)
return "\n".join(page.extract_text() for page in reader.pages)
cvs = []
for fichier in os.listdir("cvs/"):
if fichier.endswith(".pdf"):
texte = anonymiser(cv_en_texte(f"cvs/{fichier}"))
cvs.append({"id": fichier.replace(".pdf",""), "texte": texte})
Étape 4 — Prompt de scoring structuré
PROMPT = """<role>
Tu es un expert RH spécialisé dans le recrutement tech en Afrique de l'Ouest.
</role>
<fiche_poste>
Titre : Développeur Full-Stack Senior (React + Node.js)
Expérience : 5+ ans
Compétences must : TypeScript, React 18+, Node.js, PostgreSQL, REST API
Compétences nice : Next.js, Docker, CI/CD, OpenAPI, tests E2E
Langues : Français courant + Anglais technique
Localisation : Dakar ou remote compatible GMT+0
</fiche_poste>
<grille>
Experience_domaine (30) : 5+ ans React/Node noté 10, 3-5 ans noté 7, etc.
Competences_techniques (25) : combien des "must" présents
Langues (10) : FR courant + EN écrit = 10
Diplome (10) : Master/Ingénieur = 10, Licence = 7
Realisations (15) : chiffres concrets présents (utilisateurs, revenu, performance)
Geographie (10) : Dakar = 10, Afrique = 7, autres = 4
</grille>
<instruction>
Lis le CV et note chaque critère de 0 à 10. Calcule le score pondéré total sur 100.
Écris 2 lignes max expliquant les points forts, 1 ligne pour les réserves.
</instruction>
<format>
JSON strict :
{
"scores": {"experience_domaine": X, "competences_techniques": X, "langues": X, "diplome": X, "realisations": X, "geographie": X},
"total": XX,
"forces": "...",
"reserves": "..."
}
</format>
<cv>
{CV_ICI}
</cv>"""
Étape 5 — Lancer le scoring en parallèle
import anthropic, json, csv
from concurrent.futures import ThreadPoolExecutor
client = anthropic.Anthropic()
def scorer_cv(cv):
prompt = PROMPT.replace("{CV_ICI}", cv["texte"][:8000])
msg = client.messages.create(
model="claude-haiku-4-5", # rapide et pas cher, suffisant
max_tokens=600,
messages=[{"role":"user","content": prompt}]
)
try:
data = json.loads(msg.content[0].text)
return {"id": cv["id"], **data}
except:
return {"id": cv["id"], "total": 0, "error": msg.content[0].text[:200]}
with ThreadPoolExecutor(max_workers=10) as pool:
resultats = list(pool.map(scorer_cv, cvs))
# Export CSV
with open("scoring.csv", "w", newline="") as f:
w = csv.DictWriter(f, fieldnames=["id","total","forces","reserves"])
w.writeheader()
for r in sorted(resultats, key=lambda r: r.get("total",0), reverse=True):
w.writerow({k: r.get(k,"") for k in ["id","total","forces","reserves"]})
print("Top 10 :")
for r in sorted(resultats, key=lambda r: r.get("total",0), reverse=True)[:10]:
print(f" {r['id']}: {r['total']}/100")
Étape 6 — Calibrer et vérifier la qualité
- Faites scorer 20 CV aussi par un recruteur humain expérimenté
- Calculez la corrélation Spearman entre score IA et score humain
- Cible : > 0,75. Si plus bas, affinez la grille et les exemples dans le prompt
- Toujours faire relire le top 50 par un humain avant entretien
Étape 7 — Coût et temps
| Volume | Durée totale | Coût API (Haiku) |
|---|---|---|
| 100 CV | 2 minutes | ~0,30 USD |
| 1 000 CV | 15-20 min | ~3 USD |
| 10 000 CV | 2,5 h | ~30 USD |
À comparer au coût humain : 40 à 60 h de pré-sélection à 15 000 FCFA/h = 600 000 à 900 000 FCFA.
Étape 8 — Aspects juridiques et éthiques
- Informez les candidats : « Votre CV est pré-trié par une IA, décision finale humaine »
- Anonymisez nom, photo, origine, genre avant scoring
- Conservez les scores et justifications 6 mois (traçabilité RGPD/Loi sénégalaise 2008)
- Auditez annuellement : vérifiez que la distribution des scores ne défavorise aucun groupe
Erreurs courantes
- Prompt trop vague : « Note ce CV sur 10 » → notes incohérentes
- Pas d’exemples : sans few-shot, Claude dérive après 100 CV
- Utiliser Sonnet systématique : Haiku suffit pour cette tâche, 12× moins cher
- Oublier la validation humaine : l’IA pré-trie, les humains décident
Prochaines étapes
- Passer à un modèle fine-tuné si vous recrutez le même profil > 20 fois par an
- Intégrer dans un ATS (Greenhouse, Workable) via API
- Ajouter un module d’analyse de lettre de motivation et d’adéquation culturelle