Décodage METAR et règle d'alerte : trois bugs qui se cumulent
Un service d'observation exploite un script Python qui décode le groupe vent des METAR et alimente une règle d'alerte « conditions critiques » envoyée par SMS au bureau de piste. Trois symptômes sont remontés : un message 25015G27KT est restitué avec une vitesse de 15 nœuds et sans rafale ; un METAR contenant VRB03KT provoque l'arrêt du script sur une erreur de conversion ; un message de tempête codé 250115G130KT produit une direction aberrante. La règle actuelle s'écrit : if vis < 1500 or ceiling < 300 and wind > 25: - l'exploitant voulait déclencher l'alerte lorsque la visibilité ou le plafond est mauvais ET que le vent est fort. Le flux reçoit environ 12 000 observations par mois, dont 0,4 % avec rafales et 2 % avec direction variable ; 0,3 % des messages arrivent avec des champs manquants codés ///. Les observations sont horodatées en UTC, mais l'affichage du bureau de piste est en heure locale (UTC+1). Le chef de service veut une correction avant la prochaine inspection de la sécurité aérienne.
1. Identifiez les causes des trois symptômes et proposez une correction en pseudo-code, pour l'analyse du groupe vent comme pour la règle d'alerte.
Read the worked answer
Bug 1 - découpage positionnel rigide. Le code suppose le format fixe DDDSS : il lit raw[0:3] pour la direction et raw[3:5] pour la vitesse. Or un groupe vent a une longueur variable : DDDSS, DDDSSGSS, DDDSSS (vitesse à trois chiffres au-delà de 99 kt), DDDSSSGSSS, VRBSS, 00000, parfois ///. Sur 250115G130KT, raw[3:5] vaut « 11 » : d'où la direction aberrante et la vitesse fausse.
Bug 2 - direction variable assimilée à une direction. VRB ne doit jamais valoir 0 : 0 signifie « vent du nord ». Confondre les deux biaise les roses des vents et les statistiques de piste en service, et fait planter la conversion quand on passe « VRB » à int().
Bug 3 - priorité des opérateurs. vis < 1500 or ceiling < 300 and wind > 25 s'évalue comme vis < 1500 or (ceiling < 300 and wind > 25). L'intention était (vis < 1500 or ceiling < 300) and wind > 25.
Correction (pseudo-code)
MOTIF = /^(VRB|\d{3})(\d{2,3})(?:G(\d{2,3}))?$/
fonction lire_vent(groupe):
si groupe vaut "///" ou est vide : retourner (None, None, None) # inconnu ≠ 0
coeur = groupe sans le suffixe d'unité (KT ou MPS)
si le motif ne reconnaît pas coeur : journaliser et retourner (None, None, None)
dir = None si groupe1 == "VRB" sinon entier(groupe1)
vit = entier(groupe2)
raf = entier(groupe3) si groupe3 présent sinon None
retourner (dir, vit, raf)
alerte = (vis < 1500 ou plafond < 300) et vent_fort
Deux règles de codage à ajouter : ne jamais transformer une donnée absente en zéro, et tester la règle avec des parenthèses explicites plutôt qu'en comptant sur la précédence.
2. Proposez un jeu de tests de non-régression, en justifiant chaque cas par le risque qu'il couvre.
Read the worked answer
| Entrée | Attendu (direction, vitesse, rafale) | Risque couvert |
|---|---|---|
| 25015KT | (250, 15, None) | cas nominal, non-régression de base |
| 25015G27KT | (250, 15, 27) | rafale conservée, cause du symptôme 1 |
| VRB03KT | (None, 3, None) | direction variable distincte de 0, cause du symptôme 2 |
| 00000KT | (0, 0, None) | calme réel : direction 0 légitime, ne doit pas être confondue avec VRB |
| 250115G130KT | (250, 115, 130) | vitesse à trois chiffres, cause du symptôme 3 |
| /////KT | (None, None, None) | champ manquant : le pipeline doit continuer, pas s'arrêter |
| 09010MPS | rejet explicite ou conversion documentée | unité non-KT dans un flux international |
Tests sur la règle d'alerte (vis, plafond, vent) :
- (1200, 2000, 30) → True : cas nominal d'alerte.
- (8000, 2000, 30) → False : ni visibilité ni plafond mauvais.
- (1200, 2000, 10) → False : c'est le cas révélateur du bug de priorité, le code actuel renvoie True et déclenche une alerte intempestive.
- (8000, 200, 10) → False : plafond mauvais mais vent faible, conforme à l'intention.
Test d'intégration : rejouer un mois d'archives (12 000 observations) et comparer le nombre d'alertes émises avant et après correction. Tout écart doit être expliqué cas par cas ; c'est le seul moyen de détecter les erreurs de règles que les tests unitaires ne couvrent pas.
3. Au-delà des bugs, quelles décisions de conception faut-il trancher avant l'inspection ? Justifiez-les.
Read the worked answer
1. Donnée manquante : silence ou repli sécuritaire ? Sur un paramètre critique (visibilité, plafond), l'absence de donnée ne doit pas être traitée comme une valeur favorable. Un pipeline qui ignore silencieusement les /// produit une fausse impression de calme : c'est le pire état possible pour un bureau de piste. Décision recommandée : marquer l'observation « incomplète », déclencher une vérification humaine, et ne jamais laisser une valeur absente alimenter un test de seuil.
2. Référentiel temporel. 12 000 messages par mois horodatés en UTC, affichés en UTC+1 : toute la logique de calcul et de déduplication doit rester en UTC, la conversion ne se faisant qu'à l'affichage. Mélanger les deux référentiels produit des alertes décalées d'une heure, qui sont à la fois invisibles en test et spectaculaires en exploitation.
3. Critère d'alerte vent. Le seuil wind > 25 ignore la rafale, qui est précisément le paramètre dangereux pour les manœuvres au sol et l'arrimage. Proposition : alerter sur max(vitesse, rafale) et sur un écart rafale − vent moyen supérieur à 10 kt, avec une hystérésis pour éviter le spam de SMS lors d'une rafale oscillante.
4. Traçabilité. Journaliser l'observation brute, la valeur décodée et la décision de la règle. C'est ce qui permettra de répondre à l'inspection, et de distinguer plus tard une erreur de décodage d'une erreur de seuil.