Dotika MLOps · Bloc 6 — La chaîne qualitéBlock 6 — The quality line
▶ Slides▶ Slides ← Sommaire← Contents
Bloc 6 · 6 h · théorie 1h10 · lab 4h50Block 6 · 6 h · theory 1h10 · lab 4h50

La chaîne
qualité
The quality
line

CI/CD, validation des données & gouvernance. Le système est en boîte et tracé — mais chaque livraison dépend encore de la discipline humaine. Aujourd'hui, on monte la chaîne : si un feu est rouge, rien ne part. CI/CD, data validation & governance. The system is boxed and traced — but every delivery still depends on human discipline. Today we build the line: if one light is red, nothing ships.

Où on en estWhere we stand

Tout est en boîte, tout est tracé — mais livré à la mainEverything is boxed and traced — but shipped by hand

Bloc 4 : Docker. Bloc 5 : MLflow et son registry. Le système est propre — et pourtant chaque livraison repose sur la discipline d'un humain qui pense à tout, même un vendredi à 17 h 40. C'est le dernier maillon fragile. Block 4: Docker. Block 5: MLflow and its registry. The system is clean — yet every delivery rests on the discipline of a human who remembers everything, even on a Friday at 5:40 pm. The last fragile link.

Aujourd'huiToday

Le convoyeur s'allumeThe conveyor lights up

Regardez la carte : la chaîne CI/CD apparaît et alimente la boîte. Désormais, tout part d'un commit — lint, tests, validation des données, contrôle du modèle, build, push. Une machine neutre rejoue tout, à chaque fois. Look at the map: the CI/CD line appears and feeds the box. From now on, everything starts from a commit — lint, tests, data validation, model check, build, push. A neutral machine replays it all, every time.

Et surtout…And above all…

Le gate bloque un mauvais modèleThe gate blocks a bad model

La pièce maîtresse : le gate PR-AUC. Un modèle qui détecte moins bien que le champion en place ne peut plus atteindre la production — la CI refuse, le commit vire au rouge. Le contrat qualité promis au bloc 2 est enfin signé. The centerpiece: the PR-AUC gate. A model that detects worse than the reigning champion can no longer reach production — the CI refuses, the commit turns red. The quality contract promised in block 2 is finally signed.

Acte 1 · le dernier maillon humainAct 1 · the last human link

Trois choses peuvent casser, pas uneThree things can break, not one

« C'est juste un petit fix » — un scp vers la prod un vendredi à 17 h 40, et le week-end est ruiné. En logiciel classique, on teste le code. En ML, le code peut être parfait et le système mauvais quand même : "It's just a small fix" — one scp to prod on a Friday at 5:40 pm, and the weekend is ruined. In classic software, you test the code. In ML, the code can be perfect and the system still bad:

Le codeThe code
Bug dans l'API, dépendance cassée. Les tests classiques le voient : pytest, lint — le métier du logiciel depuis vingt ans.A bug in the API, a broken dependency. Classic tests catch it: pytest, lint — the software trade for twenty years.
Les donnéesThe data
Une colonne renommée en amont, des valeurs aberrantes. Aucun test de code ne le voit — il faut des tests de données.A column renamed upstream, absurd values. No code test sees it — you need data tests.
Le modèleThe model
Une PR-AUC qui régresse en silence. Ni le code ni les données ne crient — il faut un gate de performance.A PR-AUC regressing in silence. Neither code nor data screams — you need a performance gate.
CI/CD, sans le jargonCI/CD, minus the jargon

À chaque changement, une machine neutre rejoue toutes les vérifications — et si tout est vert, elle livre. CI : tester chaque commit, automatiquement. CD : construire et pousser l'artefact prêt à déployer. Le contraire exact du scp de vendredi soir. On every change, a neutral machine replays every check — and if all is green, it ships. CI: test every commit, automatically. CD: build and push the deploy-ready artifact. The exact opposite of the Friday-night scp.

Acte 2 · le convoyeur · théo 0h30Act 2 · the conveyor · theory 0h30

GitHub Actions : une recette déclenchée par un événementGitHub Actions: a recipe triggered by an event

Un workflow, c'est une fiche recette posée dans le dépôt : « quand ceci arrive (un push), fais cela (installe, teste, construis) ». Vous l'écrivez une fois — GitHub l'exécute à chaque commit, sur une machine neuve, jamais fatigué, jamais un vendredi soir. A workflow is a recipe card sitting in the repo: "when this happens (a push), do that (install, test, build)". You write it once — GitHub runs it on every commit, on a brand-new machine, never tired, never on a Friday night.

on: push  # l'événement déclencheur · the triggering event
jobs:
  quality:  # un job = une machine fraîche · one fresh machine
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: pip install -r requirements.txt
      - run: pytest  # rouge ici = tout s'arrête · red here = everything stops
ÉTAPE 1STEP 1
lint
Le style et les erreurs bêtes, en quelques secondes. Échouer vite, échouer tôt.Style and silly mistakes, in seconds. Fail fast, fail early.
ÉTAPE 2STEP 2
tests
pytest rejoue les tests de l'API du bloc 3 sur une machine neutre. Le code cassé ne va pas plus loin.pytest replays the block 3 API tests on a neutral machine. Broken code goes no further.
ÉTAPE 3STEP 3
build
L'image Docker du bloc 4, construite par le robot — plus jamais sur un laptop.The block 4 Docker image, built by the robot — never again on a laptop.
ÉTAPE 4STEP 4
push
L'image part au registry (ghcr.io), taggée par commit — traçable jusqu'à la ligne de code. Authentifiée par un secret : jamais de credentials en clair dans le YAML.The image ships to the registry (ghcr.io), tagged by commit — traceable to the line of code. Authenticated with a secret: never plain-text credentials in the YAML.
Acte 3 · valider les donnéesAct 3 · validating the data

Ça ne crashe pas. C'est pireIt doesn't crash. It's worse

Un mardi, l'équipe amont renomme une colonne. Personne ne prévient. Le pipeline tourne, l'API répond, les dashboards sont verts — et chaque prédiction est fausse. Le ML échoue en silence. La riposte : des assertions sur les données — des tests unitaires dont le sujet est le CSV, pas la fonction. One Tuesday, the upstream team renames a column. Nobody says a word. The pipeline runs, the API answers, the dashboards are green — and every prediction is wrong. ML fails in silence. The response: data assertions — unit tests whose subject is the CSV, not the function.

FAMILLE 1FAMILY 1
Le schémaThe schema
Les bonnes colonnes, les bons types, rien en plus, rien en moins. La défense contre le schema drift — le mardi silencieux.The right columns, the right types, nothing more, nothing less. The defense against schema drift — the silent Tuesday.
FAMILLE 2FAMILY 2
Les bornesThe bounds
Amount ≥ 0, Class dans {0, 1}, dates plausibles. Tout ce qui a un domaine de validité se vérifie.Amount ≥ 0, Class in {0, 1}, plausible dates. Anything with a validity domain gets checked.
FAMILLE 3FAMILY 3
Les manquantsThe missing values
Un taux de null qui explose = un capteur qui meurt en amont. On fixe un seuil, et on le tient.A null rate that explodes = a sensor dying upstream. Set a threshold, and hold it.
FAMILLE 4FAMILY 4
Les distributionsThe distributions
La part de fraudes passe de 0,17 % à 3 % ? Le schéma est intact, mais quelque chose a changé le monde. Avant-goût du drift, bloc 7.Fraud share jumps from 0.17% to 3%? The schema is intact, but something changed the world. A foretaste of drift, block 7.
Branché dans la CIWired into the CI

La suite de validation tourne en job CI à chaque changement de données : une assertion échoue → exit 1 → commit rouge, avec le nom de l'assertion violée. Un CSV qui change en silence, désormais, fait du bruit. The validation suite runs as a CI job on every data change: one assertion fails → exit 1 → red commit, naming the violated assertion. A CSV that changes in silence now makes noise.

Acte 4 · le gate PR-AUCAct 4 · the PR-AUC gate

Aucun modèle qui régresse ne passeNo model that regresses gets through

Le code est testé, les données sont testées — reste le troisième suspect : le modèle. Le gate entraîne le challenger sur un échantillon en CI, compare sa PR-AUC à celle du champion (le modèle en production, béni par le registry du bloc 5), et bloque si ça régresse. Avec une tolérance ε : le bruit d'entraînement n'est pas une régression — un gate trop strict finit débranché, et un gate débranché ne protège personne. The code is tested, the data is tested — one suspect remains: the model. The gate trains the challenger on a sample in CI, compares its PR-AUC to the champion's (the production model, blessed by block 5's registry), and blocks on regression. With an ε tolerance: training noise is not a regression — a gate that's too strict ends up unplugged, and an unplugged gate protects no one.

Essayez : un commit, six feuxTry it: one commit, six lights

Le premier bouton lance un commit sain — les feux passent au vert un à un. Le second livre un modèle dégradé : tout passe… jusqu'au gate. The first button fires a healthy commit — the lights turn green one by one. The second ships a degraded model: everything passes… until the gate.

lint
tests
data check
gate PR-AUC
build
push
La boucle se boucleThe loop closes

Au bloc 2, on promettait : « la PR-AUC deviendra une barrière automatique dans la CI ». C'est fait — la métrique choisie parce que classe rare devient un contrat exécutable. Personne ne peut plus livrer un détecteur qui détecte moins bien : la machine tient la promesse à votre place. In block 2, we promised: "the PR-AUC will become an automatic barrier in the CI". Done — the metric chosen because of the rare class becomes an executable contract. Nobody can ship a detector that detects worse: the machine keeps the promise on your behalf.

Acte 5 · gouvernance · théo 0h40Act 5 · governance · theory 0h40

Votre modèle décide qui a le droit de payerYour model decides who gets to pay

Un régulateur bancaire ne demande pas si votre modèle est bon : il demande de prouver comment il décide, sur quelles données, avec quelles limites. La gouvernance, c'est cette preuve — écrite avant qu'on la réclame. A banking regulator doesn't ask whether your model is good: it asks you to prove how it decides, on what data, with what limits. Governance is that proof — written before anyone asks.

La model cardThe model card
La carte d'identité du modèle : données d'entraînement, métriques et là où elles s'effondrent, limites, usage prévu. Si le modèle était un médicament, ce serait sa notice.The model's identity card: training data, metrics and where they collapse, limits, intended use. If the model were a medicine, this would be its leaflet.
L'audit trailThe audit trail
Qui a déployé quoi, quand : Git (le code) + MLflow (le modèle) + la CI (la livraison) = la chaîne de preuve. Trois outils déjà en place — la gouvernance ne coûte presque rien quand le MLOps est propre.Who deployed what, when: Git (the code) + MLflow (the model) + the CI (the delivery) = the chain of evidence. Three tools already in place — governance costs almost nothing when the MLOps is clean.
IA responsableResponsible AI
Qui bloque-t-on à tort (le biais, mesuré par segment — rappel bloc 2) ? Sait-on expliquer une décision ? Un humain peut-il reprendre la main ? Et l'Europe encadre l'IA : documentation, traçabilité, supervision humaine seront exigées — le secteur bancaire est en première ligne. Le MLOps propre est une avance sur la conformité.Who do we block wrongly (bias, measured per segment — see block 2)? Can we explain a decision? Can a human take over? And Europe is regulating AI: documentation, traceability, human oversight will be required — banking is on the front line. Clean MLOps is a head start on compliance.
La mission du blocThe block's mission

« Un commit suffit — tests, données, modèle, build : tout se vérifie seul. Si un feu est rouge, rien ne part. » "A commit is enough — tests, data, model, build: everything checks itself. If one light is red, nothing ships."

Plus personne ne livre à la main. La qualité n'est plus une promesse — c'est un mécanisme. Nobody ships by hand anymore. Quality is no longer a promise — it's a mechanism.

Lab · 4h50Lab · 4h50

À vous de jouerYour turn

Quatre missions, en binômes — un qui écrit, un qui lit les logs du runner, on échange à la mi-temps. Les durées viennent du plan de cours : Four missions, in pairs — one writes, one reads the runner logs, swap at half-time. Durations come straight from the course plan:

1h30
La chaîne.github/workflows/ci.yml : lint + pytest, build de l'image Docker, push vers ghcr.io avec un secret. Cassez un test exprès : le commit doit virer au rougeThe line.github/workflows/ci.yml: lint + pytest, Docker image build, push to ghcr.io with a secret. Break a test on purpose: the commit must turn red
pratiquehands-on
1h30
Valider la datasrc/data/validate.py : schéma, bornes, manquants, distribution des classes, en job CI. Renommez une colonne : la chaîne doit crierValidate the datasrc/data/validate.py: schema, bounds, missing values, class distribution, as a CI job. Rename a column: the line must scream
pratiquehands-on
1h20
Le gate PR-AUCsrc/eval/gate.py : entraîner sur échantillon, comparer au champion avec tolérance ε, bloquer la régression. Sabotez le modèle : le gate doit refuserThe PR-AUC gatesrc/eval/gate.py: train on a sample, compare to the champion with ε tolerance, block regression. Sabotage the model: the gate must refuse
pratiquehands-on
0h30
La model carddocs/model_card.md : données, métriques, limites, usage prévu, supervision humaine. Une page, honnête, relue par un autre binômeThe model carddocs/model_card.md: data, metrics, limits, intended use, human oversight. One honest page, reviewed by another pair
pratiquehands-on
Definition of done

Un push → badge vert, image fraîche au registry. Une colonne renommée ou un modèle dégradé → CI rouge, avec un message exploitable. Et une model card commitée à côté du code. Cette chaîne devient le seul chemin vers la prod — c'est elle qui alimentera Kubernetes au bloc 7. One push → green badge, fresh image in the registry. A renamed column or a degraded model → red CI, with a usable message. And a model card committed next to the code. This line becomes the only road to production — it's what feeds Kubernetes in block 7.

Pas à pas · en autonomieStep by step · self-paced

Faites-le vous-même, de A à ZDo it yourself, start to finish

Tout le lab, rejouable seul : chaque commande, chaque fichier en entier, un point de contrôle par étape. Prérequis : le repo fraud-detection/ des blocs 2–5 (Dockerfile du bloc 4, tests pytest du bloc 3, requirements.txt), un compte GitHub, git — et idéalement la CLI gh. The whole lab, replayable on your own: every command, every file in full, one checkpoint per step. Prerequisites: the fraud-detection/ repo from blocks 2–5 (block 4's Dockerfile, block 3's pytest tests, requirements.txt), a GitHub account, git — and ideally the gh CLI.

Mission 1 — La chaîne (1h30)Mission 1 — The line (1h30)

1Pousser le repo sur GitHubPush the repo to GitHub

D'abord le .gitignore : le venv, les runs MLflow et surtout creditcard.csv (144 Mo, au-dessus de la limite GitHub) ne se commitent jamais. Puis le push — option A avec la CLI, option B via le site. The .gitignore first: the venv, the MLflow runs and above all creditcard.csv (144 MB, above GitHub's limit) never get committed. Then the push — option A with the CLI, option B via the website.

cd fraud-detection
cat >> .gitignore <<'EOF'
.venv/
mlruns/
__pycache__/
data/creditcard.csv
EOF
git add -A
git commit -m "chore: prepare repo for CI"

# option A: GitHub CLI
gh auth login
gh repo create fraud-detection --private --source . --push

# option B: create an empty private repo on github.com, then
git remote add origin https://github.com/YOUR_USER/fraud-detection.git
git push -u origin main
Vous devriez voir : la page github.com/YOUR_USER/fraud-detection avec vos fichiers — et PAS creditcard.csv.You should see: the github.com/YOUR_USER/fraud-detection page with your files — and NOT creditcard.csv.
2Écrire .github/workflows/ci.ymlWrite .github/workflows/ci.yml

La recette complète : quatre jobs — lint + tests (ceux du bloc 3), validation des données, gate PR-AUC, et le build-push Docker qui n'a lieu que si les trois autres sont verts. Avant de pousser, vérifiez en local : pip install pytest ruff puis ruff check src et pytest. The full recipe: four jobs — lint + tests (block 3's), data validation, PR-AUC gate, and the Docker build-push that only happens if the other three are green. Before pushing, check locally: pip install pytest ruff then ruff check src and pytest.

name: ci

on:
  push:
    branches: [main]
  pull_request:

jobs:
  lint-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.11"
          cache: pip
      - run: pip install -r requirements.txt pytest ruff
      - run: ruff check src
      - run: pytest

  data-validation:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.11"
          cache: pip
      - run: pip install pandas
      - run: python scripts/validate_data.py data/sample.csv

  model-gate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.11"
          cache: pip
      - run: pip install pandas scikit-learn
      - run: python scripts/gate.py

  build-push:
    runs-on: ubuntu-latest
    needs: [lint-test, data-validation, model-gate]
    permissions:
      contents: read
      packages: write
    steps:
      - uses: actions/checkout@v4
      - uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      - uses: docker/build-push-action@v6
        with:
          context: .
          push: true
          tags: ghcr.io/${{ github.repository }}:${{ github.sha }}
Vous devriez voir : le fichier créé dans .github/workflows/ — le push vient à l'étape 3.You should see: the file created under .github/workflows/ — the push comes in step 3.
3Pousser et lire l'onglet ActionsPush and read the Actions tab

Deux jobs vont rester rouges : les scripts qu'ils appellent n'existent pas encore. C'est voulu — les missions 2 et 3 les fournissent, et vous verrez la chaîne passer au vert morceau par morceau. Two jobs will stay red: the scripts they call don't exist yet. That's intended — missions 2 and 3 provide them, and you'll watch the line turn green piece by piece.

git add .github/workflows/ci.yml
git commit -m "ci: quality line - lint, data check, gate, build"
git push
Vous devriez voir : onglet Actions — lint-test vert, data-validation et model-gate rouges (scripts absents, normal), build-push skipped.You should see: Actions tab — lint-test green, data-validation and model-gate red (missing scripts, expected), build-push skipped.

Mission 2 — Valider la data (1h30)Mission 2 — Validate the data (1h30)

4Créer l'échantillon commité data/sample.csvCreate the committed sample data/sample.csv

La CI a besoin de données, mais on ne commite JAMAIS le dataset complet — seulement un échantillon stratifié de 5 000 lignes qui préserve le taux de fraude. Créez scripts/make_sample.py : The CI needs data, but you NEVER commit the full dataset — only a 5,000-row stratified sample that preserves the fraud rate. Create scripts/make_sample.py:

import pandas as pd
from sklearn.model_selection import train_test_split

df = pd.read_csv("data/creditcard.csv")
sample, _ = train_test_split(
    df, train_size=5000, stratify=df["Class"], random_state=42)
sample.to_csv("data/sample.csv", index=False)
print(sample["Class"].value_counts())
mkdir -p scripts
python scripts/make_sample.py
wc -l data/sample.csv
Vous devriez voir : 5001 lignes (l'en-tête + 5 000), et value_counts affiche 8 ou 9 fraudes — le taux de 0,17 % est préservé.You should see: 5001 lines (header + 5,000), and value_counts shows 8 or 9 frauds — the 0.17% rate is preserved.
5Écrire scripts/validate_data.pyWrite scripts/validate_data.py

Les quatre familles de l'acte 3, en assertions exécutables : schéma (31 colonnes, types numériques), bornes (Amount ≥ 0, Class ∈ {0, 1}), manquants (zéro NaN), distribution (taux de fraude entre 0,05 % et 1 %). Une violation → exit 1 → commit rouge, avec un message clair. Act 3's four families, as executable assertions: schema (31 columns, numeric types), bounds (Amount ≥ 0, Class ∈ {0, 1}), missing values (zero NaN), distribution (fraud rate between 0.05% and 1%). One violation → exit 1 → red commit, with a clear message.

import sys

import pandas as pd

EXPECTED_COLUMNS = ["Time"] + [f"V{i}" for i in range(1, 29)] + ["Amount", "Class"]


def fail(message):
    print(f"DATA VALIDATION FAILED: {message}")
    sys.exit(1)


def main(path):
    df = pd.read_csv(path)

    if list(df.columns) != EXPECTED_COLUMNS:
        fail(f"schema mismatch, expected 31 known columns, got: {list(df.columns)}")
    for col in EXPECTED_COLUMNS:
        if not pd.api.types.is_numeric_dtype(df[col]):
            fail(f"column {col} is not numeric")
    if df.isna().any().any():
        fail("dataset contains NaN values")
    if (df["Amount"] < 0).any():
        fail("negative Amount found")
    if not df["Class"].isin([0, 1]).all():
        fail("Class must be 0 or 1")
    fraud_rate = df["Class"].mean()
    if not 0.0005 <= fraud_rate <= 0.01:
        fail(f"fraud rate {fraud_rate:.4%} outside [0.05%, 1%]")

    print(f"data OK: {len(df)} rows, fraud rate {fraud_rate:.4%}")


if __name__ == "__main__":
    main(sys.argv[1] if len(sys.argv) > 1 else "data/sample.csv")
python scripts/validate_data.py data/sample.csv
Vous devriez voir : data OK: 5000 rows, fraud rate 0.1800% (ou une valeur proche).You should see: data OK: 5000 rows, fraud rate 0.1800% (or a value close to it).
6Brancher, casser, réparerWire it, break it, fix it

Poussez, vérifiez le vert — puis cassez volontairement : un Amount négatif dans l'échantillon doit faire crier la chaîne. Réparez en régénérant l'échantillon propre. Push, check the green — then break it on purpose: one negative Amount in the sample must make the line scream. Fix it by regenerating a clean sample.

git add scripts/ data/sample.csv
git commit -m "ci: data validation + committed sample"
git push

# break it on purpose: one negative Amount
python -c "import pandas as pd; d = pd.read_csv('data/sample.csv'); d.loc[d.index[0], 'Amount'] = -50; d.to_csv('data/sample.csv', index=False)"
git commit -am "test: corrupt the sample on purpose"
git push

# repair
python scripts/make_sample.py
git commit -am "fix: restore a clean sample"
git push
Vous devriez voir : trois runs Actions — data-validation vert, puis rouge avec « negative Amount found », puis vert à nouveau.You should see: three Actions runs — data-validation green, then red with "negative Amount found", then green again.

Mission 3 — Le gate PR-AUC (1h20)Mission 3 — The PR-AUC gate (1h20)

7Le baseline et scripts/gate.pyThe baseline and scripts/gate.py

D'abord le champion à battre — créez metrics/baseline.json (valeur plancher au départ) : First, the champion to beat — create metrics/baseline.json (a floor value to start):

{
  "pr_auc": 0.60,
  "updated": "2026-07-06",
  "reason": "floor value - replace with your first local gate run"
}

Puis le gate : il entraîne le challenger sur l'échantillon, mesure sa PR-AUC et refuse toute régression au-delà d'une tolérance ε = 0,02 — le bruit d'entraînement n'est pas une régression. Créez scripts/gate.py : Then the gate: it trains the challenger on the sample, measures its PR-AUC and refuses any regression beyond an ε = 0.02 tolerance — training noise is not a regression. Create scripts/gate.py:

import json
import sys

import pandas as pd
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import average_precision_score
from sklearn.model_selection import train_test_split
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler

EPSILON = 0.02
BASELINE_PATH = "metrics/baseline.json"


def main():
    df = pd.read_csv("data/sample.csv")
    X = df.drop(columns=["Class"])
    y = df["Class"]
    X_tr, X_te, y_tr, y_te = train_test_split(
        X, y, test_size=0.2, stratify=y, random_state=42)

    model = Pipeline([
        ("scaler", StandardScaler()),
        ("clf", LogisticRegression(class_weight="balanced", max_iter=1000)),
    ])
    model.fit(X_tr, y_tr)
    proba = model.predict_proba(X_te)[:, 1]
    pr_auc = average_precision_score(y_te, proba)

    with open(BASELINE_PATH) as f:
        baseline = json.load(f)["pr_auc"]

    print(f"challenger pr_auc={pr_auc:.4f}  baseline={baseline:.4f}  epsilon={EPSILON}")
    if pr_auc < baseline - EPSILON:
        print("GATE FAILED: model regresses beyond tolerance")
        sys.exit(1)
    print("GATE PASSED")


if __name__ == "__main__":
    main()
mkdir -p metrics
python scripts/gate.py
Vous devriez voir : challenger pr_auc=0.7xxx baseline=0.6000 puis GATE PASSED. Reportez la valeur imprimée dans baseline.json : c'est le nouveau champion. Règle : toute mise à jour du baseline est volontaire, commitée, et justifiée dans le message de commit.You should see: challenger pr_auc=0.7xxx baseline=0.6000 then GATE PASSED. Copy the printed value into baseline.json: that's the new champion. Rule: any baseline update is deliberate, committed, and justified in the commit message.
8Chaîne complète : verte, rouge, verteFull line: green, red, green

Poussez : les quatre jobs doivent passer, build-push compris. Puis sabotez le gate — un baseline gonflé simule un challenger qui régresse — et réparez d'un revert. Push: all four jobs must pass, build-push included. Then sabotage the gate — an inflated baseline simulates a regressing challenger — and repair with a revert.

git add scripts/gate.py metrics/baseline.json
git commit -m "ci: PR-AUC gate against committed baseline"
git push

# sabotage: inflate the baseline - the gate must refuse
python -c "import json; json.dump({'pr_auc': 0.99}, open('metrics/baseline.json', 'w'))"
git commit -am "test: inflated baseline on purpose"
git push

# repair: restore the honest baseline
git revert --no-edit HEAD
git push
Vous devriez voir : run 1 — quatre jobs verts et l'image dans l'onglet Packages, taggée par SHA de commit ; run 2 — model-gate rouge avec GATE FAILED ; run 3 — tout revient au vert.You should see: run 1 — four green jobs and the image in the Packages tab, tagged by commit SHA; run 2 — model-gate red with GATE FAILED; run 3 — everything back to green.

Mission 4 — La model card (0h30)Mission 4 — The model card (0h30)

9Écrire docs/model_card.mdWrite docs/model_card.md

La carte d'identité du modèle — une page, honnête. Les chiffres ci-dessous sont des exemples : remplacez-les par les valeurs de vos runs MLflow (bloc 5), y compris les segments. The model's identity card — one honest page. The numbers below are examples: replace them with the values from your MLflow runs (block 5), segments included.

# Model card - fraud-detector

## Model
- Type: scikit-learn pipeline (StandardScaler + LogisticRegression, class_weight=balanced)
- Version: MLflow registry "fraud-detector", alias @production (v2)
- Owner: your-name - date: 2026-07-06

## Training data
- Source: creditcard.csv (Kaggle ULB) - 284 807 transactions, 492 frauds (0.17%)
- Features: Time, V1-V28 (anonymized PCA components), Amount
- Split: 80/20 stratified, random_state=42 - scaler fitted on train only

## Metrics (test set)
- PR-AUC global: 0.79
- PR-AUC amounts over 1000 EUR: 0.71 - weakest segment, see limits
- PR-AUC night (00h-06h): 0.74
- Threshold 0.42, chosen to minimize business cost:
  a missed fraud costs its amount, a false alert costs 15 EUR

## Limits
- 2013 data, European cardholders: drift expected elsewhere (monitored in block 7)
- Anonymized features: individual decisions are hard to explain to a customer
- Weaker on amounts over 1000 EUR - exactly where an error costs most

## Intended use
- Real-time scoring of card transactions, alerts reviewed by a human analyst
- Out of scope: automatic blocking without human review, creditworthiness scoring

## Contact
- Team fraud-ml: fraud-ml@dotika.example - escalation: see the on-call runbook
mkdir -p docs
git add docs/model_card.md
git commit -m "docs: model card v1"
git push
Vous devriez voir : GitHub rend la card en page lisible, et la CI reste verte — la carte vit à côté du code, versionnée comme lui.You should see: GitHub renders the card as a readable page, and the CI stays green — the card lives next to the code, versioned like it.
Si ça coinceWhen it breaks

denied: permission_denied au push ghcr ? Le job n'a pas le droit d'écrire : gardez permissions: packages: write dans build-push, et Settings → Actions → Workflow permissions → Read and write.
pytest: command not found ? Chaque job est une machine neuve : installez pytest et ruff dans le job (pip install ... pytest ruff), pas seulement sur votre laptop.
Le push refuse un fichier trop gros ? creditcard.csv a été commité (144 Mo, limite GitHub 100 Mo) : gardez-le dans .gitignore — seul data/sample.csv (≈ 2 Mo) se commite.
Faut-il créer un secret pour ghcr ? Non : GITHUB_TOKEN est fourni automatiquement à chaque run. Les secrets (Settings → Secrets) servent aux registries externes — jamais de credentials en clair dans le YAML.
denied: permission_denied pushing to ghcr? The job isn't allowed to write: keep permissions: packages: write in build-push, and Settings → Actions → Workflow permissions → Read and write.
pytest: command not found? Every job is a brand-new machine: install pytest and ruff inside the job (pip install ... pytest ruff), not just on your laptop.
Push refused, file too large? creditcard.csv got committed (144 MB, GitHub limit 100 MB): keep it in .gitignore — only data/sample.csv (about 2 MB) gets committed.
Do you need a secret for ghcr? No: GITHUB_TOKEN is provided automatically on every run. Secrets (Settings → Secrets) are for external registries — never plain-text credentials in the YAML.