Todos los artículos

Más allá de la previsión: agregando alertas meteorológicas oficiales para la seguridad al aire libre

Publicado 18 oct 20245 min de lectura
  • MeteoTrail
  • Ingeniería
  • Montaña

Ideas clave

  • El feed Atom de Meteoalarm y el formato CAP de cada país son incómodos de consumir directamente, así que los normalizamos una sola vez para una lista fija de regiones en lugar de dejar que cada producto los analice por su cuenta.
  • El servicio falla cerrado ante claves de API ausentes (401 por defecto) y distingue los fallos parciales (200 con un array de errores) de las caídas totales del origen (503).
  • A diferencia de MeteoTrail, esto es infraestructura interna, solo accesible con clave de API, sin interfaz pública, y los productos que dependen de ella heredan sus fallos en silencio.

Después de lanzar MeteoTrail —nuestro producto de previsión a nivel de punto de control para eventos deportivos al aire libre— la mayor parte de nuestra atención se fue en la precisión del modelo: viento, precipitación y temperatura en un punto concreto de una montaña, a una hora concreta. Lo que no llegué a entender del todo hasta Val d'Aran by UTMB es cuánto peso organizativo puede acabar cargando un software una vez que los directores de carrera lo tienen delante para tomar decisiones de ir o no ir. Esa experiencia cambió cómo pienso sobre cualquier cosa que construyamos que toque decisiones de seguridad, aunque sea indirectamente.

En los meses siguientes, un problema más pequeño y aparentemente sin relación reaparecía una y otra vez. Las alertas meteorológicas oficiales del gobierno —del tipo «aviso naranja por viento para esta región a partir de mañana a las 06:00»— son públicas y genuinamente útiles, pero nadie había construido nada para que las consumiéramos de forma programática. Cada producto al que le importaba tenía que ir a buscarlas por su cuenta. Este artículo trata sobre el pequeño servicio interno que construimos para que eso dejara de pasar.

1. Datos públicos, mal pensados para consumirse

Europa ya resolvió, más o menos, el problema del descubrimiento para las alertas meteorológicas. Meteoalarm agrega las alertas oficiales de todas las agencias meteorológicas nacionales del continente y las republica como un feed Atom, uno por país. Eso es genuinamente útil: no tienes que saber a cuál de las treinta y pico agencias consultar. Pero el propio feed es un resumen: título, un código de región aproximado, un color de severidad, un enlace hacia fuera.

El contenido real de una alerta —qué fenómeno, qué umbrales, qué ventana temporal, en qué área precisa— vive en un payload CAP (Common Alerting Protocol), publicado por la agencia de cada país en su propio dialecto del estándar. Para España, eso es AEMET. «Estándar», en el mundo de las alertas, todavía deja mucho margen para peculiaridades propias de cada agencia en códigos de área, vocabularios de severidad y estructura XML.

Consumir esto correctamente significa dos peticiones, dos formatos, y reconciliar ambos en algo sobre lo que un producto pueda ramificar lógica. Está bien hacerlo una vez. No está bien pedirle a cada equipo interno que lo haga por su cuenta.

2. Qué construimos exactamente

Construimos el Spain Meteo Alerts Aggregator: un pequeño servicio FastAPI con un único trabajo —obtener, filtrar, normalizar y cachear—. No intenta cubrir toda España. Funciona sobre una lista fija de regiones relevantes para nuestros productos de turismo al aire libre y de montaña: Lleida, Aran (Val d'Aran), y varias zonas costeras de Mallorca —sur, tramontana, interior, levante y noroeste—.

El pipeline por petición es sencillo:

  1. Obtener el feed Atom de Meteoalarm España.
  2. Filtrar las entradas hasta las regiones de la lista.
  3. Para cada alerta que coincida, obtener el payload CAP de AEMET con el detalle real.
  4. Normalizar todo —la forma del feed de Meteoalarm y la forma del CAP de AEMET— en un único esquema JSON estable.
  5. Agrupar el resultado por región, y luego por severidad (roja, naranja, amarilla, desconocida).
  6. Descartar de la respuesta cualquier región que ahora mismo no tenga ninguna alerta activa, para que quien la consuma no tenga que filtrar ruido vacío por su cuenta.

Una forma ilustrativa y simplificada de lo que devuelve GET /alerts:

{
  "generated_at": "2024-10-18T07:15:00Z",
  "regions": {
    "aran": {
      "red": [],
      "orange": [
        {
          "id": "aemet-es-es610-20241018-0001",
          "phenomenon": "wind",
          "onset": "2024-10-19T06:00:00Z",
          "expires": "2024-10-19T18:00:00Z",
          "headline": "Orange wind warning"
        }
      ],
      "yellow": [],
      "unknown": []
    },
    "mallorca-tramontana": {
      "red": [],
      "orange": [],
      "yellow": [
        { "id": "aemet-es-illesbalears-20241018-0007", "phenomenon": "rain" }
      ],
      "unknown": []
    }
  },
  "errors": []
}

Nada exótico. El valor no está en ningún paso concreto, está en haber hecho la reconciliación una sola vez, en un solo sitio, en lugar de seis veces repartidas en seis productos.

3. Fallar cerrado, y fallar parcialmente

Dos decisiones de diseño aquí vinieron directamente de lecciones que no quería tener que volver a aprender.

Primera: la autenticación. /alerts, /areas y /areas/{country} requieren todos una cabecera X-API-Key. /health y / se quedan abiertos, porque los monitores de uptime necesitan algún sitio donde llamar sin credenciales. Lo que me importa es qué pasa cuando el servicio no tiene ninguna clave de API configurada —un despliegue mal configurado, un secreto que falta, lo que sea—. Rechaza toda petición protegida con 401. No cae a acceso abierto. Un servicio sin claves configuradas es un servicio que se olvidó de configurar claves, no un servicio que pretende ser público.

Segunda, y más sutil: GET /alerts distingue dos modos de fallo muy distintos. Si la obtención del feed de Meteoalarm falla por completo, o el servicio no puede construir un resumen del que se fiaría, devuelve 503 —«no tengo nada fiable que contarte, no actúes sobre esta respuesta»—. Pero si el feed va bien y la mayoría de las regiones se resuelven correctamente mientras que, digamos, una consulta al CAP de AEMET da timeout, devuelve 200 con las regiones que sí se resolvieron y un array errors no vacío describiendo qué no se resolvió —«esto es lo que sé, y esto es lo que no, tú decides qué hacer con el hueco»—. Los productos que consumen esto pueden reaccionar de forma muy distinta a esos dos casos.

4. Por qué esto no es MeteoTrail

Este servicio nunca tendrá una landing page. No hay interfaz, no hay alta pública, no hay copy de marketing: solo un backend FastAPI interno al que un puñado de nuestros propios productos llama con una clave. Precisamente por eso el diseño de fallo cerrado y fallo parcial importa más aquí de lo que podría importar en algo de cara al público.

Cuando un producto público se rompe, un usuario ve un error y recarga. Cuando se rompe una pieza de infraestructura interna de la que otros servicios dependen en silencio —o peor, se degrada silenciosamente y devuelve algo que parece correcto pero no lo es— cada producto construido encima hereda ese fallo sin saberlo. Ese es un listón mucho menos indulgente que «¿tiene buena pinta la página de previsión?». Es también, creo, el listón correcto para cualquier cosa que esté silenciosamente por debajo de decisiones que tocan la seguridad de alguien, aunque sea a una distancia.

No construimos esto porque fuera difícil o interesante. Lo construimos porque, después de Val d'Aran by UTMB, prefiero pasarme una tarde diseñando de antemano para el caso de fallo antes que enterarme más tarde, por un producto que depende de esto, de que no lo habíamos hecho.