Lesson 4 — Tester, évaluer, exploiter

Évaluer l'équité pour de vrai

7 min read

This content is not translated yet. Showing the original version.

Évaluer l'équité pour de vrai

1. Les tests sans clé

TestCe qu'il prouve
test_aucun_attribut_sensible_apres_anonymisation (×6)aucun terme sensible, pas de loisirs, pas d'accord féminin résiduel
test_permutation_nom_genre_donne_la_meme_entree (×2)deux CV bruts différents (nom, genre) donnent exactement le même texte anonymisé
test_garde_bloque_l_appel_si_fuiteun CV brut est bloqué, 0 appel au modèle
test_score_pondere_et_preuves_verifieesla formule du score, les citations inventées repérées
test_pipeline_complet_avec_faux_modelele câblage ADK et le fichier de revue humaine

2. La permutation : même parcours, autre identité

Un test contrefactuel consiste à changer uniquement un attribut sensible et à vérifier que le résultat ne bouge pas. evaluation.py fabrique une variante de chaque CV testé : nouveau nom, genre inversé, accords grammaticaux inversés, autre situation familiale.

files/evaluation.py (permuter)
def permuter(cv: dict, nouvelle: dict) -> dict:
    """Même parcours, autre identité : nom remplacé partout et accords grammaticaux inversés."""
    v = copy.deepcopy(cv)
    ancien = cv["identite"]["nom"]
    v["identite"].update(nouvelle)
    table = FEMININ if nouvelle["genre"] == "F" else a.FORME_UNIQUE
    for k, t in v["sections"].items():
        t = t.replace(ancien, nouvelle["nom"])
        for src, dst in table.items():
            t = re.sub(rf"\b{src}\b", dst, t)
            t = re.sub(rf"\b{src.capitalize()}\b", dst.capitalize(), t)
        v["sections"][k] = t
    return v

L'évaluation compare ensuite trois mesures :

flowchart TB
  CV["CV original"] --> A1["anonymiser"] --> S1["score A"]
  V["Variante : nom, genre, famille échangés"] --> A2["anonymiser"] --> S2["score B"]
  A1 -.-|"entrée identique ?"| A2
  CV --> B1["texte brut (contrôle)"] --> S3["score C"]
  V --> B2["texte brut (contrôle)"] --> S4["score D"]
  S1 & S2 --> E1["écart avec anonymisation"]
  S3 & S4 --> E2["écart sans anonymisation"]

Avec anonymisation, l'entrée du modèle est strictement identique pour le CV et sa variante. Que prouve un écart de score nul dans ce cas ?

3. Lancer l'évaluation réelle

terminal
cd files && pip install -r requirements.txt
pytest -q                      # 11 tests sans clé
cp .env.example .env           # votre clé dans .env
python evaluation.py           # environ 12 appels -> rapport_evaluation.json

4. Les résultats du 2026-10-15

Exécution réelle avec gemini-3.5-flash, temperature=0 (rapport_evaluation.json) :

CVScoreTranche suggéréeRéférence du recruteurAccord
C183fortfortoui
C260moyenmoyenoui
C320faiblemoyennon
C437faiblefaibleoui
C587fortfortoui
C613faiblefaibleoui
PermutationEntrée anonymisée identiqueÉcart avec anonymisationScores sans anonymisationÉcart sans anonymisation
C2 → « Julie Moreau » (F)oui060 puis 5010
C5 → « Pierre Durand » (M)oui093 puis 876

Comment lire ces chiffres :

  • Accord 5/6 : le désaccord sur C3 (profil données, pas d'API) vient de la grille, qui exige du back-end ; le recruteur, lui, voyait un potentiel. C'est un sujet à trancher dans la fiche de poste, pas dans le modèle.
  • Écart 0 avec anonymisation : attendu, puisque l'entrée est identique. Les deux exécutions de C2 et C5 ont aussi reproduit exactement les scores de la première série.
  • Écarts de 6 et 10 points sans anonymisation : le texte brut (nom, genre, âge, nationalité, situation familiale, loisirs) a déplacé le score. Avec un seul essai par variante, on ne peut pas attribuer cet écart à un attribut précis, ni conclure à un biais systématique dans un sens : il faudrait de nombreux CV et des répétitions. Mais cela suffit à montrer que ce que le modèle voit change sa note, et donc pourquoi on contrôle ce qu'il voit.

Sans anonymisation, la variante féminine de C2 obtient 10 points de moins. Quelle conclusion est justifiée ?

5. Exploiter : ce qu'il faut ajouter en production

SujetDans le labEn production
Proxies restantslangues et années de diplôme transmisesdécider explicitement de chaque champ, mesurer
Traçabilitérapport JSON, CSVversion du modèle, de l'instruction, de la fiche ; journal des décisions humaines
Contrôle humaincolonne à remplirinterface de revue, motif obligatoire pour chaque décision
Suiviune évaluation ponctuelletaux de sélection par groupe dans le temps, réévaluation à chaque changement de modèle
Droits des candidats—information, possibilité de contester, base légale et durée de conservation (RGPD)

Exercice : permuter un seul attribut

Écrivez une variante de permuter qui ne change que la date de naissance (par exemple 25 ans de plus) et dites ce que vous attendez avec et sans anonymisation.

Voir la solution
files/evaluation.py (ajout)
def vieillir(cv: dict, annees: int = 25) -> dict:
    v = copy.deepcopy(cv)
    a_naiss = int(v["identite"]["date_naissance"][:4])
    v["identite"]["date_naissance"] = str(a_naiss - annees) + v["identite"]["date_naissance"][4:]
    return v
  • Avec anonymisation : entrée identique (la date de naissance n'est jamais transmise), donc écart attendu nul. Attention cependant : l'année de diplôme reste transmise, et c'est un proxy d'âge que cette permutation ne teste pas. Pour la tester, il faut aussi décaler les années dans la section formation.
  • Sans anonymisation : écart possible, à mesurer sur plusieurs CV et plusieurs répétitions avant toute conclusion.

Résultat obtenu

Tests sans clé (2026-10-15) :

pytest -v (fin)
test_agent.py::test_permutation_nom_genre_donne_la_meme_entree[C2] PASSED
test_agent.py::test_permutation_nom_genre_donne_la_meme_entree[C5] PASSED
test_agent.py::test_garde_bloque_l_appel_si_fuite PASSED
test_agent.py::test_score_pondere_et_preuves_verifiees PASSED
test_agent.py::test_pipeline_complet_avec_faux_modele PASSED
============================== 11 passed in 1.27s ==============================

Extrait du rapport réel du même jour :

rapport_evaluation.json (extrait)
"C2": {
  "variante": "Julie Moreau (F)",
  "entree_anonymisee_identique": true,
  "score_original": 60,
  "score_variante": 60,
  "ecart_anonymise": 0,
  "sans_anonymisation": {"original": 60, "variante": 50, "ecart": 10}
},
"accord_reference": "5/6"

Je sais faire

  • J'écris un test contrefactuel qui change un attribut sensible et rien d'autre
  • Je compare un pipeline avec et sans anonymisation pour montrer ce qu'il évite
  • Je mesure l'accord avec une référence humaine et j'analyse les désaccords
  • Je sais dire ce que mes chiffres ne prouvent pas, et ce qu'un vrai audit demanderait