stoneray/PPML_FASTAPI
0
1# FlyOnTime — Gestion des erreurs, code sharing et historisation S32 3## Objectif4 5Ce document explique le fonctionnement de la chaîne **FastAPI → flight lookup → ETL → modèles → Streamlit** pour le projet **FlyOnTime**, avec un focus sur :6 7- la gestion des erreurs métier après un input utilisateur8- la gestion des cas de **code sharing**9- l’historisation des statuts de requête et des logs sur **Amazon S3**10- le rôle exact de chaque fichier modifié11- les cas de test à valider12 13---14 15## Vue d’ensemble du flux16 17### 1. Input utilisateur côté Streamlit18 19L’utilisateur saisit :20 21- `flight_number`22- `date`23- `departure_airport`24- `arrival_airport` (optionnel ou exploité selon les cas)25 26Streamlit envoie ensuite une requête HTTP `POST /predict` à l’API FastAPI.27 28---29 30### 2. FastAPI reçoit la demande31 32Le endpoint `/predict` de `app.py` :33 341. valide le payload352. appelle `GlobalRunSingleFlight.py`363. récupère le `request_id`374. laisse le pipeline produire les fichiers de sortie de la requête385. lit le fichier `flight_request_status.json`396. décide si la requête est un succès, un warning métier ou une erreur bloquante407. renvoie ensuite :41 - soit une prédiction normale42 - soit un `400` avec un message lisible pour l’utilisateur43 44---45 46### 3. GlobalRunSingleFlight orchestre le pipeline47 48`GlobalRunSingleFlight.py` crée un **dossier par requête** sous :49 50```text51flight_lookup/output/<request_id>/52```53 54Puis il :55 561. lance `CleanCSV.py`572. lance `aerodatabox_Single_flight.py`583. arrête le pipeline si une erreur métier bloquante est détectée594. poursuit le reste des scripts si le vol est exploitable605. génère le parquet `SignoffFlightsDataset_Single_<REQUEST_ID>.parquet`616. appelle `S3_Upload_Single.py`62 63---64 65### 4. Aerodatabox gère la logique métier amont66 67`aerodatabox_Single_flight.py` est le **point central de décision métier** pour :68 69- vol introuvable70- réponse API invalide71- problème réseau/API72- problème d’horaire73- cas de **code sharing**74- plusieurs vols possibles dans une journée75 76C’est aussi lui qui écrit le fichier de statut de la requête.77 78---79 80## Fichiers clés et responsabilités81 82### `app.py`83 84Responsabilités :85 86- exposer l’API FastAPI87- appeler `GlobalRunSingleFlight.py`88- lire `flight_request_status.json`89- transformer les erreurs métier en `HTTP 400`90- transmettre le `warning_message` à Streamlit91- lancer ensuite l’ETL et les modèles si tout va bien92 93### `GlobalRunSingleFlight.py`94 95Responsabilités :96 97- créer le `request_id`98- créer le dossier de requête99- injecter les variables d’environnement (`REQUEST_ID`, `REQUEST_DIR`, `RUN_DATE`, etc.)100- lancer les scripts du pipeline101- arrêter le pipeline tôt si le statut de requête est bloquant102- lancer l’upload S3 des logs même en cas d’erreur amont103 104### `aerodatabox_Single_flight.py`105 106Responsabilités :107 108- appeler l’API AeroDataBox109- extraire les vols retournés110- filtrer le bon vol111- détecter les cas de code share112- écrire :113 - `flight_request_status.json`114 - `API_Single_ERR.log`115 116### `S3_Upload_Single.py`117 118Responsabilités :119 120- uploader le parquet raw `SignoffFlightsDataset_Single_<REQUEST_ID>.parquet`121- uploader `flight_request_status.json`122- uploader `API_Single_ERR.log`123- fonctionner aussi en mode `logs_only`124 125### Streamlit126 127Responsabilités :128 129- afficher le succès de prédiction130- afficher les erreurs métier via `st.error(...)`131- afficher les warnings métier via `st.warning(...)`132 133---134 135## Fichier de statut de requête136 137### Nom138 139```text140flight_request_status.json141```142 143### Emplacement local144 145```text146flight_lookup/output/<request_id>/flight_request_status.json147```148 149### Emplacement S3150 151```text152s3://ppml2026/raw/<RUN_DATE>/<REQUEST_ID>/flight_request_status.json153```154 155### Rôle156 157Ce fichier est la **source de vérité métier** de la requête.158 159Il sert à :160 161- arrêter le pipeline proprement162- afficher un message commercial lisible au user163- transmettre un warning de code share164- historiser la requête dans S3165 166### Exemple de contenu — erreur bloquante167 168```json169{170 "request_id": "requete_AF0000_20260422_184500",171 "status": "error_flight_not_found",172 "error_code": "FLIGHT_NOT_FOUND",173 "user_message": "Vol introuvable. Veuillez vérifier le numéro de vol, la date, l’horaire et l’aéroport de départ.",174 "warning_message": null,175 "matched_flight_number": null,176 "generated_at": "2026-04-22T18:45:00"177}178```179 180### Exemple de contenu — code sharing181 182```json183{184 "request_id": "requete_SQ1894_20260422_184500",185 "status": "success_with_warning_codeshare",186 "error_code": null,187 "user_message": "Vol trouvé avec succès.",188 "warning_message": "Le vol saisi correspond à un vol en partage de code. Les données ont été retrouvées sous la référence AF7364.",189 "matched_flight_number": "AF7364",190 "matched_scheduled_departure": "2026-04-22T06:45:00+02:00",191 "matched_departure_airport": "CDG",192 "matched_arrival_airport": "NCE",193 "generated_at": "2026-04-22T18:45:00"194}195```196 197---198 199## Fichier de log texte200 201### Nom202 203```text204API_Single_ERR.log205```206 207### Emplacement local208 209```text210flight_lookup/output/<request_id>/API_Single_ERR.log211```212 213### Emplacement S3214 215```text216s3://ppml2026/raw/<RUN_DATE>/<REQUEST_ID>/API_Single_ERR.log217```218 219### Rôle220 221Ce fichier sert surtout au **debug dev**.222 223Il peut contenir :224 225- erreur HTTP renvoyée par l’API226- réponse brute API si utile227- raison métier du rejet228- détails de mismatch horaire229- contexte de filtrage230 231Ce fichier n’est **pas** la source principale affichée au user. C’est le `flight_request_status.json` qui pilote l’affichage.232 233---234 235## Gestion des erreurs métier236 237### Principe238 239Les erreurs métier sont décidées côté backend, puis affichées côté Streamlit.240 241Autrement dit :242 243- **FastAPI décide du message**244- **Streamlit décide de la manière de l’afficher**245 246### Cas bloquants recommandés247 248#### `error_flight_not_found`249 250Quand aucun vol exploitable n’est trouvé.251 252Message user :253 254> Vol introuvable. Veuillez vérifier le numéro de vol, la date, l’horaire et l’aéroport de départ.255 256#### `error_time_mismatch`257 258Quand des vols sont trouvés mais qu’aucun ne correspond à l’horaire saisi dans la tolérance définie.259 260Message user :261 262> Plusieurs vols ont été trouvés, mais aucun ne correspond précisément à l’horaire renseigné. Veuillez vérifier l’heure saisie.263 264#### `error_api_unavailable`265 266Quand le service externe est momentanément indisponible ou renvoie une réponse invalide.267 268Message user :269 270> Le service de recherche de vol est momentanément indisponible. Merci de réessayer dans quelques instants.271 272---273 274## Gestion du code sharing275 276### Principe produit277 278Un **code share** n’est pas traité comme une erreur.279 280C’est un **warning métier**.281 282Exemple :283 284- l’utilisateur saisit `SQ1894`285- la référence opérationnelle retrouvée est `AF7364`286 287Dans ce cas :288 289- on continue le pipeline290- on fait la prédiction normalement291- on renvoie un `warning_message`292- on loggue cette information dans `flight_request_status.json`293 294### Pourquoi ce n’est pas une erreur295 296Parce que :297 298- le vol existe299- la prédiction reste possible300- il faut juste informer proprement l’utilisateur pour éviter un doute sur la fiabilité du produit301 302### Message recommandé303 304> Le vol saisi correspond à un vol en partage de code. Les données ont été retrouvées sous la référence AF7364.305 306### Où la logique est gérée307 308La logique de code sharing est gérée principalement dans :309 310```text311aerodatabox_Single_flight.py312```313 314Avec trois moments clés :315 3161. récupération de `codeshares` depuis la réponse API3172. détection du match via `codeshares`3183. écriture du `warning_message` dans `flight_request_status.json`319 320FastAPI ne fait ensuite que relayer cette information au frontend.321 322---323 324## Gestion de plusieurs vols dans une journée325 326Le système gère le cas où plusieurs vols sont renvoyés pour le même numéro dans une journée avec le filtrage suivant :327 3281. match sur le numéro de vol3292. match sur l’aéroport de départ3303. match sur l’aéroport d’arrivée si fourni3314. match sur l’horaire demandé, avec une tolérance de **30 minutes**332 333### Limite actuelle334 335Si plusieurs vols restent strictement à égalité après ce filtrage, le pipeline prend le premier résultat restant.336 337C’est suffisant pour le MVP, mais ce n’est pas une résolution parfaite de toutes les ambiguïtés extrêmes.338 339---340 341## Organisation S3 retenue342 343### Buckets et préfixes344 345Bucket :346 347```text348ppml2026349```350 351Préfixes utilisés :352 353- `raw/`354- `processed/`355 356### Logique métier retenue357 358#### `raw/`359Contient les artefacts bruts / lookup / signoff, ainsi que les logs d’erreur et le statut de requête.360 361Exemple :362 363```text364ppml2026/365 raw/366 2026-04-22/367 requete_AF7306_20260422_153424/368 SignoffFlightsDataset_Single_requete_AF7306_20260422_153424.parquet369 flight_request_status.json370 API_Single_ERR.log371```372 373#### `processed/`374Contient le parquet transformé pour l’inférence modèle.375 376Exemple :377 378```text379ppml2026/380 processed/381 2026-04-22/382 requete_AF7306_20260422_153424/383 single_flight_model_input_requete_AF7306_20260422_153424.parquet384```385 386### Pourquoi cette séparation387 388- `raw/` = état brut et métier amont389- `processed/` = état transformé prêt pour le modèle390 391Les warnings et erreurs sont donc logiquement historisés dans `raw/`.392 393---394 395## Flux complet d’une requête396 397### Cas 1 — succès normal398 3991. Streamlit envoie le payload à FastAPI4002. FastAPI lance `GlobalRunSingleFlight.py`4013. `aerodatabox_Single_flight.py` trouve le vol4024. `flight_request_status.json` est écrit avec `status=success`4035. le reste du pipeline se déroule4046. `Signoff...parquet` est généré4057. `S3_Upload_Single.py` uploade le raw sur S34068. l’ETL produit `single_flight_model_input...parquet`4079. le parquet processed est uploadé sur S340810. FastAPI renvoie la prédiction40911. Streamlit affiche le résultat410 411### Cas 2 — faux vol412 4131. Streamlit envoie le payload4142. FastAPI lance `GlobalRunSingleFlight.py`4153. `aerodatabox_Single_flight.py` ne trouve aucun vol exploitable4164. `flight_request_status.json` est écrit avec `error_flight_not_found`4175. `API_Single_ERR.log` est écrit4186. `GlobalRunSingleFlight.py` arrête le pipeline tôt4197. `S3_Upload_Single.py` uploade les logs dans `raw/<date>/<request_id>/`4208. FastAPI lit le statut et renvoie un `400`4219. Streamlit affiche le message commercial avec `st.error(...)`422 423### Cas 3 — code share424 4251. Streamlit envoie le payload4262. `aerodatabox_Single_flight.py` trouve un vol exploitable via `codeshares`4273. `flight_request_status.json` est écrit avec `success_with_warning_codeshare`4284. le pipeline continue4295. la prédiction est calculée normalement4306. FastAPI lit le warning et le renvoie à Streamlit4317. Streamlit affiche `st.warning(...)` puis la prédiction432 433---434 435## Affichage côté Streamlit436 437### Cas succès438 439- `st.success("Analyse réalisée avec succès.")`440- affichage des scores de prédiction441 442### Cas warning code share443 444- `st.warning(data["warning_message"])`445- puis affichage des scores normalement446 447### Cas erreur métier448 449- lecture de `response.json()["detail"]`450- affichage avec `st.error(...)`451 452### Point important453 454Il ne faut pas se contenter de `response.raise_for_status()` si on veut afficher un message user propre.455 456Il faut explicitement lire le `detail` du `400` pour afficher un message commercial lisible.457 458---459 460## Variables d’environnement utiles461 462### Variables pipeline / requête463 464- `REQUEST_ID`465- `REQUEST_DIR`466- `REQUEST_OUTPUT_ROOT`467- `REQUEST_OUTPUT_SINGLE`468- `REQUEST_OUTPUT_GREVES`469- `REQUEST_OUTPUT_METEO`470- `REQUEST_OUTPUT_JF`471- `RUN_DATE`472- `ENABLE_S3_UPLOAD`473 474### Variables AWS / HF475 476- `AWS_ACCESS_KEY_ID`477- `AWS_SECRET_ACCESS_KEY`478- `AWS_DEFAULT_REGION`479- `S3_BUCKET_NAME`480- `S3_PREFIX`481 482### Variables MLflow483 484- `MLFLOW_TRACKING_URI`485 486### Variables API externe487 488- `API_KEY`489 490---491 492## Codes de statut recommandés493 494### Statuts succès495 496- `success`497- `success_with_warning_codeshare`498 499### Statuts d’erreur500 501- `error_flight_not_found`502- `error_time_mismatch`503- `error_api_unavailable`504 505---506 507## Bonnes pratiques retenues508 509### 1. Le message métier vient du backend510 511FastAPI doit être la source du message commercial.512 513### 2. Streamlit ne réinvente pas la logique métier514 515Il affiche simplement :516 517- erreur518- warning519- succès520 521### 3. Le fichier de statut pilote le comportement522 523`flight_request_status.json` sert de contrat entre :524 525- le script AeroDataBox526- l’orchestrateur GlobalRunSingleFlight527- FastAPI528- Streamlit529- S3530 531### 4. S3 sert d’historique, pas de source temps réel532 533FastAPI lit le statut localement pendant l’exécution.534S3 sert d’archive et de traçabilité.535 536---537 538## Cas de test recommandés539 540### Test 1 — vol valide541 542Attendu :543 544- HTTP 200545- prédiction affichée546- parquet raw présent sur S3547- parquet processed présent sur S3548- `flight_request_status.json` avec `status=success`549 550### Test 2 — faux vol551 552Exemple : `AF0000`553 554Attendu :555 556- HTTP 400557- message user lisible558- `flight_request_status.json` dans `raw/...`559- `API_Single_ERR.log` dans `raw/...`560 561### Test 3 — code share562 563Exemple métier : `SQ1894` → `AF7364`564 565Attendu :566 567- HTTP 200568- warning affiché dans Streamlit569- prédiction affichée570- `flight_request_status.json` avec `success_with_warning_codeshare`571 572### Test 4 — mauvais horaire573 574Attendu :575 576- HTTP 400577- message d’erreur sur l’horaire578- status = `error_time_mismatch`579 580---581 582## Limites actuelles583 584- si plusieurs vols restent exactement équivalents après tous les filtres, le premier est retenu585- la tolérance horaire est fixée à 30 minutes et peut devoir être ajustée586- les logs S3 servent à l’historisation, mais pas à l’exécution temps réel587- la qualité du code share dépend de ce que renvoie l’API externe588 589---590 591## Résumé final592 593Le système mis en place repose sur une logique simple et robuste :594 595- `aerodatabox_Single_flight.py` décide du statut métier596- `GlobalRunSingleFlight.py` orchestre et arrête tôt si besoin597- `S3_Upload_Single.py` archive les artefacts raw et les logs598- `app.py` transforme cela en réponse API propre599- Streamlit affiche élégamment erreurs, warnings et succès600 601Le **code sharing** n’est pas traité comme une erreur, mais comme un **warning métier** loggé dans `flight_request_status.json`, relayé par FastAPI et affiché par Streamlit.602 603L’historisation S3 permet ensuite de conserver une trace fiable de chaque requête par `request_id`.604 