33from airflow .providers .docker .operators .docker import DockerOperator
44from airflow .operators .python import PythonOperator
55from airflow .exceptions import AirflowFailException
6+ from docker .types import Mount
67import mlflow
8+ from airflow .utils .trigger_rule import TriggerRule
79from mlflow .tracking import MlflowClient
810import requests
911import os
12+ import re
13+ from pathlib import Path
1014
1115
16+ DATA_VOLUME_NAME = os .getenv ("DATA_VOLUME_NAME" , "mlops_accidents_accidents-data" )
1217MLFLOW_TRACKING_URI = "http://mlflow:5000"
1318EXPERIMENT_NAME = "Gravité_Accidents"
1419MODEL_NAME = "Modèle_Gravité_Accidents"
1520DOCKER_NETWORK = "mlops_accidents_default"
1621
1722MAX_DROP_ALLOWED = 0.05
1823
24+
1925default_args = {
20- ' owner' : ' mlops_team' ,
21- ' start_date' : datetime (2026 , 1 , 1 ),
22- ' retries' : 1 ,
26+ " owner" : " mlops_team" ,
27+ " start_date" : datetime (2026 , 1 , 1 ),
28+ " retries" : 1 ,
2329}
2430
31+
2532def check_metrics_and_alert (** context ):
2633 """
2734 Récupère le f1_score du dernier run de l'expérience 'Gravité_Accidents'
@@ -32,26 +39,34 @@ def check_metrics_and_alert(**context):
3239
3340 experiment = client .get_experiment_by_name (EXPERIMENT_NAME )
3441 if not experiment :
35- raise AirflowFailException (f"L'expérience '{ EXPERIMENT_NAME } ' n'a pas été trouvée dans MLflow." )
42+ raise AirflowFailException (
43+ f"L'expérience '{ EXPERIMENT_NAME } ' n'a pas été trouvée dans MLflow."
44+ )
3645
3746 runs = client .search_runs (
3847 experiment_ids = [experiment .experiment_id ],
3948 order_by = ["attributes.start_time DESC" ],
40- max_results = 1
49+ max_results = 1 ,
4150 )
4251
4352 if not runs :
44- raise AirflowFailException (f"Aucun run trouvé pour l'expérience { EXPERIMENT_NAME } ." )
53+ raise AirflowFailException (
54+ f"Aucun run trouvé pour l'expérience { EXPERIMENT_NAME } ."
55+ )
4556
4657 current_run = runs [0 ]
4758 current_f1 = current_run .data .metrics .get ("f1_score" )
4859 current_run_id = current_run .info .run_id
4960
50- context ['ti' ].xcom_push (key = 'current_run_id' , value = current_run_id )
51- print (f"Nouveau modèle entraîné détecté - Run ID: { current_run_id } | F1-Score: { current_f1 } " )
61+ context ["ti" ].xcom_push (key = "current_run_id" , value = current_run_id )
62+ print (
63+ f"Nouveau modèle entraîné détecté - Run ID: { current_run_id } | F1-Score: { current_f1 } "
64+ )
5265
5366 if current_f1 is None :
54- raise AirflowFailException ("Le dernier run MLflow n'a pas enregistré de métrique 'f1_score'." )
67+ raise AirflowFailException (
68+ "Le dernier run MLflow n'a pas enregistré de métrique 'f1_score'."
69+ )
5570
5671 try :
5772 prod_model_version = client .get_model_version_by_alias (MODEL_NAME , "champion" )
@@ -67,6 +82,7 @@ def check_metrics_and_alert(**context):
6782 except mlflow .exceptions .MlflowException :
6883 print ("Aucun modèle marqué '@champion' trouvé. Première promotion du projet." )
6984
85+
7086def promote_model_to_champion (** context ):
7187 """
7288 Associe l'alias 'champion' à la dernière version du modèle validé.
@@ -75,18 +91,22 @@ def promote_model_to_champion(**context):
7591 mlflow .set_tracking_uri (MLFLOW_TRACKING_URI )
7692 client = MlflowClient ()
7793
78- run_id = context ['ti' ].xcom_pull (key = ' current_run_id' , task_ids = ' evaluate_metrics' )
94+ run_id = context ["ti" ].xcom_pull (key = " current_run_id" , task_ids = " evaluate_metrics" )
7995
8096 filter_string = f"run_id='{ run_id } '"
8197 versions = client .search_model_versions (filter_string )
8298
8399 if not versions :
84- raise AirflowFailException (f"Aucune version de modèle trouvée dans le Registry pour le run { run_id } ." )
100+ raise AirflowFailException (
101+ f"Aucune version de modèle trouvée dans le Registry pour le run { run_id } ."
102+ )
85103
86104 latest_version = versions [0 ].version
87105
88106 client .set_registered_model_alias (MODEL_NAME , "champion" , latest_version )
89- print (f"Succès : Le modèle '{ MODEL_NAME } ' version { latest_version } est maintenant désigné comme '@champion'." )
107+ print (
108+ f"Succès : Le modèle '{ MODEL_NAME } ' version { latest_version } est maintenant désigné comme '@champion'."
109+ )
90110
91111
92112def reload_predict_service ():
@@ -97,23 +117,20 @@ def reload_predict_service():
97117 bento_url = "http://ml-api:3000/reload_model"
98118 try :
99119 response = requests .post (bento_url , timeout = 15 )
100- if response .status_code == 200 :
101- print ("Le conteneur 'ml-api' a mis à jour son modèle avec succès." )
102- else :
103- print (f"Le service ml-api a répondu avec un code erreur : { response .status_code } " )
120+ response .raise_for_status ()
121+ print ("Le conteneur 'ml-api' a mis à jour son modèle avec succès." )
104122 except Exception as e :
105- print (f"Notification non envoyée à ml-api (Vérifie si l'API expose ce endpoint) : { e } " )
123+ raise AirflowFailException (f"Échec du rechargement de ml-api : { e } " )
106124
107125
108126with DAG (
109- ' mlops_accident_gravity_pipeline' ,
127+ " mlops_accident_gravity_pipeline" ,
110128 default_args = default_args ,
111- description = ' Pipeline d\ ' entraînement pour la gravité des accidents' ,
112- schedule = ' @monthly' ,
129+ description = " Pipeline d'entraînement pour la gravité des accidents" ,
130+ schedule = " @monthly" ,
113131 catchup = False ,
114132 tags = ["accidents" ],
115133) as dag :
116-
117134 # task_make_dataset = DockerOperator(
118135 # task_id='docker_make_dataset',
119136 # image='make_dataset:latest',
@@ -123,28 +140,43 @@ def reload_predict_service():
123140 # mounts=[Mount(source=f"{BASE_DIR}/mlruns", target="/app/mlruns", type="bind")]
124141 # )
125142
143+ task_preprocess = DockerOperator (
144+ task_id = "preprocess" ,
145+ image = "mlops_accidents-preprocess:latest" ,
146+ api_version = "auto" ,
147+ auto_remove = True ,
148+ mount_tmp_dir = False ,
149+ network_mode = DOCKER_NETWORK ,
150+ mounts = [Mount (source = f"{ DATA_VOLUME_NAME } " , target = "/app/data" , type = "volume" )],
151+ )
152+
126153 task_train = DockerOperator (
127- task_id = 'docker_train' ,
128- image = ' mlops_accidents-train:latest' ,
129- api_version = ' auto' ,
154+ task_id = "train" ,
155+ image = " mlops_accidents-train:latest" ,
156+ api_version = " auto" ,
130157 auto_remove = True ,
158+ mount_tmp_dir = False ,
131159 network_mode = DOCKER_NETWORK ,
132- environment = {'MLFLOW_TRACKING_URI' : MLFLOW_TRACKING_URI },
160+ environment = {"MLFLOW_TRACKING_URI" : MLFLOW_TRACKING_URI },
161+ mounts = [Mount (source = f"{ DATA_VOLUME_NAME } " , target = "/app/data" , type = "volume" )],
133162 )
134163
135164 task_evaluate = PythonOperator (
136- task_id = ' evaluate_metrics' ,
165+ task_id = " evaluate_metrics" ,
137166 python_callable = check_metrics_and_alert ,
138167 )
139168
140169 task_promote = PythonOperator (
141- task_id = ' promote_model' ,
170+ task_id = " promote_model" ,
142171 python_callable = promote_model_to_champion ,
143172 )
144173
145174 task_reload = PythonOperator (
146- task_id = 'reload_predict_service' ,
147- python_callable = reload_predict_service
175+ task_id = "reload_predict_service" ,
176+ python_callable = reload_predict_service ,
177+ trigger_rule = TriggerRule .ALL_DONE ,
148178 )
149179
150- task_train >> task_evaluate >> task_promote >> task_reload
180+ task_preprocess >> task_train >> task_evaluate
181+ task_evaluate >> task_promote
182+ task_evaluate >> task_reload
0 commit comments