La API estaba rota, así que rastreé el mapa que funcionaba
geo-hazard ingiere tres feeds públicos sobre España: avisos meteorológicos de AEMET, terremotos del IGN e incendios de Copernicus EFFIS. Los dos primeros estuvieron en vivo en un día. EFFIS tardó tres, y no porque el código fuera difícil: porque durante dos de esos días estuve haciéndole la pregunta correcta a un servidor roto, en la dirección equivocada.
Un 200 que miente, una capa que revienta
La documentación de EFFIS apunta a servicios OGC estándar. La realidad,
capturada a las malas: el servidor se cuelga con HTTP/2, WFS 2.0.0 devuelve
502, 1.1.0 se cuelga, y solo WFS 1.0.0 contesta. Y cuando por fin le pedí la
capa que quería - effis.nrt.ba.poly, áreas quemadas en near-real-time -
contestó al instante, con HTTP 200, conteniendo msPostGISLayerGetItems(): Query error. Las capas de focos simplemente hacían timeout. Mismo resultado
al día siguiente.
Así que pausé el job de incendios en producción - un UPDATE de una fila
poniendo su next_run_at a infinity - y la API sirvió cero filas de
incendios. El vacío honesto gana a los datos inventados.
Rastrea el producto, no la documentación
Entonces alguien (yo, enseñando el proyecto) abrió el visor oficial de EFFIS: incendios por todas partes, áreas quemadas de tres fuentes satelitales, actualizándose tan contento. Los datos existían y se estaban sirviendo. Solo que yo no preguntaba donde pregunta el visor.
Un navegador headless capturando el tráfico de red lo respondió en minutos.
El visor pinta los incendios como tiles raster (WMTS) - lo que significa
que ver incendios en un mapa no demuestra nada sobre ninguna API vectorial.
Su API de apoyo resultó ser un FastAPI diminuto con cuatro endpoints, ninguno
de datos. Pero los nombres de las capas de tiles - nrt.ba.today,
s3.hs.today - eran la pista. Mismo servidor, mismo MapServer, distinto
mapfile: el visor se alimenta de /gwis, la documentación apunta a
/effis. En /gwis la familia completa existe y contesta:
nrt.ba.poly.{today,week,month,season} con polígonos frescos, all.hs.* con
focos de esta misma mañana.
Las rarezas son el contrato
Lo que significa “funcionar” en el mundo OGC de hace veinte años mereció su propio ADR:
- El parámetro
TIMEse ignora en silencio. La ventana temporal va codificada en el nombre de la capa:.today,.weeky.seasonson capas distintas. - La salida GeoJSON serializa las coordenadas como
[lat, lon]- invertidas - mientras que el parámetrobboxde la petición quiere el orden normallon,lat. El swap de ejes vive en exactamente un sitio de mi parser, junto a un comentario que explica por qué. - Los fallos del backend vuelven como HTTP 200 con un
ServiceExceptionReporten XML. Clasifica por el cuerpo, no por el status: ese es transitorio (reintenta en el siguiente poll), un 404 es un cambio de contrato (que mire un humano). - Existen capas de archivo con atributos ricos (
viirs.hs.query, con potencia radiativa y confianza) pero abarcan toda la historia desde 2019, y cualquier filtro por fecha hace timeout. Inservibles para sync; anotado y adiós.
Capturé payloads reales como fixtures commiteados, escribí el parser contra ellos, y el driver llegó a producción esa misma tarde.
El primer payload real es un test que no has ejecutado
La primera sincronización en vivo trajo 7.970 focos y 92 polígonos de área
quemada, y se estrelló de inmediato: the number of query arguments cannot exceed 32767. Mi upsert mandaba el lote entero en una sola sentencia - unas
8.000 filas por 9 columnas son 72.000 parámetros, y asyncpg corta la sentencia
en 32.767. Las otras dos fuentes nunca se acercaron (230 avisos, 14 sismos),
así que 174 tests y un driver fake jamás habían ejercitado el límite. El fix
es aburrido: trocear el lote dentro de la misma transacción. La contención
funcionó como estaba diseñada - job fallido, backoff, cero corrupción - y el
test de regresión inserta ahora 4.001 filas porque ese es el número honesto
más pequeño que habría fallado.
Tres lecciones. Cuando el endpoint documentado está roto pero el producto que tiene encima funciona, rastrea el producto. En los servicios OGC veteranos el contrato real es folklore: captura payloads en vivo como fixtures y escribe los parsers contra eso, no contra la spec. Y los drivers fake te llevan a producción, pero el primer payload real sigue siendo un test, lo hayas escrito o no.