fix(backend,airflow,ml): applique les corrections de revue sur la PR #162
This commit is contained in:
@@ -18,16 +18,6 @@ from airflow.timetables.trigger import CronTriggerTimetable
|
||||
# Le backend possède son propre environnement uv dans l'image Airflow (ADR 0008).
|
||||
COMMANDE_BACKEND = "cd /opt/backend && env -u VIRTUAL_ENV uv run --no-sync python -m"
|
||||
|
||||
# L'API Mock ne renvoie pas un flux au rythme naturel : elle répartit exactement `limit`
|
||||
# lectures, espacées uniformément, sur toute la fenêtre demandée (vérifié empiriquement :
|
||||
# une fenêtre d'1h avec `limit=1000` renvoie 1000 lectures espacées de 3,6s à l'intérieur de
|
||||
# cette heure, pas une lecture horaire). Comme la fenêtre de ce DAG est toujours 1h
|
||||
# (`interval=timedelta(hours=1)` ci-dessous), `limit=1` est ce qui produit une lecture par
|
||||
# heure, alignée sur l'heure, cohérente avec le grain horaire du reste du schéma
|
||||
# (`period_minutes=60`, historique CSV à 1 ligne/heure). Un `limit` plus grand ici fabriquerait
|
||||
# des lectures infra-horaires, incompatibles avec les lags positionnels de `build_features`.
|
||||
LIMITE_LECTURES = 1
|
||||
|
||||
# Deux reprises donnent trois tentatives au total. Même dans le pire cas, l'exécution reste
|
||||
# inférieure au pas horaire du DAG.
|
||||
NOMBRE_REPRISES = 2
|
||||
@@ -36,7 +26,11 @@ PLAFOND_PAR_TENTATIVE = timedelta(minutes=10)
|
||||
|
||||
# L'intervalle est déclaré explicitement pour ne pas dépendre de la valeur du paramètre Airflow
|
||||
# `create_cron_data_intervals`. Le déclenchement à :45 laisse quinze minutes avant `ml_score`,
|
||||
# exécuté à l'heure pile, puis avant `alertes`, exécuté à :15.
|
||||
# exécuté à l'heure pile, puis avant `alertes`, exécuté à :15. Conséquence vérifiée empiriquement
|
||||
# sur l'API Mock (cf. `limit_for_window()` dans `app.etl.mock_api_import`) : la fenêtre importée
|
||||
# est `[:45, :45)`, donc chaque lecture atterrit à :45, pas à :00, un décalage constant sur
|
||||
# toute la série, sans effet sur les lags positionnels ni sur les jointures en aval (`ml_score`
|
||||
# vise la dernière lecture + 1h, `derive` joint à l'égalité), cf. 40-data.md.
|
||||
PLANIFICATION = CronTriggerTimetable(
|
||||
"45 * * * *",
|
||||
timezone="UTC",
|
||||
@@ -58,8 +52,11 @@ with DAG(
|
||||
bash_command=(
|
||||
f"{COMMANDE_BACKEND} app.etl.mock_api_import "
|
||||
"--start-time \"{{ data_interval_start.strftime('%Y-%m-%dT%H:%M:%S') }}\" "
|
||||
"--end-time \"{{ data_interval_end.strftime('%Y-%m-%dT%H:%M:%S') }}\" "
|
||||
f"--limit {LIMITE_LECTURES}"
|
||||
"--end-time \"{{ data_interval_end.strftime('%Y-%m-%dT%H:%M:%S') }}\""
|
||||
# Pas de --limit : app.etl.mock_api_import.limit_for_window() le dérive de la
|
||||
# fenêtre (une lecture/heure), et refuse une fenêtre qui ne couvre pas un nombre
|
||||
# entier d'heures ou dépasse le plafond de l'API. Porter la règle dans le code,
|
||||
# pas dans ce DAG, évite qu'un appel manuel oublie de la respecter.
|
||||
),
|
||||
retries=NOMBRE_REPRISES,
|
||||
retry_delay=DELAI_ENTRE_REPRISES,
|
||||
|
||||
@@ -118,7 +118,8 @@ def test_mock_api_import_uses_the_airflow_data_interval(dagbag: DagBag) -> None:
|
||||
|
||||
assert "--start-time \"{{ data_interval_start.strftime('%Y-%m-%dT%H:%M:%S') }}\"" in commande
|
||||
assert "--end-time \"{{ data_interval_end.strftime('%Y-%m-%dT%H:%M:%S') }}\"" in commande
|
||||
assert "--limit 1" in commande
|
||||
# Pas de --limit : app.etl.mock_api_import.limit_for_window() le dérive de la fenêtre.
|
||||
assert "--limit" not in commande
|
||||
|
||||
|
||||
@pytest.mark.parametrize("task_id", ["detection", "recommandations"])
|
||||
|
||||
Reference in New Issue
Block a user