Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/adrianaarang/climapp/llms.txt

Use this file to discover all available pages before exploring further.

ClimApp’s service layer is split across four modules: WeatherAPIService retrieves raw observations from the AEMET open-data API, normalizar_datos_aemet() transforms those observations into a standard internal format, AlertService evaluates the normalised data against risk thresholds, and RetryService provides all HTTP calls with automatic retry logic. Together these modules isolate external API concerns from the Flask route handlers.

WeatherAPIService

WeatherAPIService is the single entry point for live AEMET data. Instantiating it requires the AEMET_API_KEY environment variable to be set; the constructor raises a ValueError immediately if the key is absent.

Constructor

WeatherAPIService()
On construction the class:
  1. Reads AEMET_API_KEY from the environment via os.getenv().
  2. Creates a retry-enabled requests.Session by calling get_retry_session().
  3. Sets self.base_url to the AEMET conventional-observations endpoint.
If AEMET_API_KEY is not set in your environment (or .env file), the constructor will raise ValueError: AEMET_API_KEY no encontrada en .env and the application will not start.

_obtener_datos_crudos()

Internal method. Performs the two-step AEMET request pattern:
  1. Step 1 — metadata request: GET to base_url with the API key in the headers. The response body contains a datos URL pointing to the actual dataset.
  2. Step 2 — data request: GET to the datos URL to download all current conventional observations as a JSON list.
Returns the parsed list of observation dicts, or an empty list on any error.
Both requests use timeout=20 and benefit from the session’s automatic retry strategy. The method never raises; it logs the error and returns [].

obtener_clima_por_coordenadas(user_lat, user_lon)

Finds the AEMET station closest to the supplied GPS coordinates by computing the Haversine distance to every observation in the raw dataset, then returns that station’s raw observation dict.
user_lat
number
required
User latitude in decimal degrees (e.g. 40.4168).
user_lon
number
required
User longitude in decimal degrees (e.g. -3.7038).
Returns the raw AEMET observation dict for the nearest station, or None if no observations were received. Stations with missing or malformed lat/lon fields are silently skipped.

obtener_clima_por_id(station_id)

Placeholder method reserved for future implementation of station-by-ID lookups. Currently returns None.

Module-level bridge function

def obtener_clima_por_coordenadas(lat, lon):
    service = WeatherAPIService()
    return service.obtener_clima_por_coordenadas(lat, lon)
app.py imports and calls this module-level function directly. It instantiates a fresh WeatherAPIService on every call, keeping route handlers decoupled from the class.

normalizar_datos_aemet(data)

Defined in services/normalizer_service.py. Transforms a raw AEMET observation dict (or list of dicts) into ClimApp’s standard internal format and appends alert labels.
normalizar_datos_aemet(data) -> dict
data
object | array
required
Raw observation data from AEMET. If a list is passed, the last element is used. If None or empty, returns {"error": "No hay datos disponibles"}.

Field mapping

AEMET keyNormalised keyFallback
ubiestacion"Desconocida"
fintfecha"N/A"
tatemperatura0
hrhumedad0
vvviento0
prespresion0
preclluvia0
All numeric fields are cast to float. Missing AEMET keys fall back to 0. After building the base dict, alert_service.evaluar_alertas() is called and its return value is stored under the "alertas" key. On any unhandled exception, the function returns {"error": "Error al procesar los datos"}.

AlertService thresholds

AlertService.evaluar_alertas() runs after normalisation. It applies the following rules and returns a list of active alert strings:
Alerts are evaluated in order; only the first matching threshold fires.
ThresholdAlert label
temperatura >= 40.0 °C"ROJA"
temperatura >= 35.0 °C"NARANJA"
temperatura <= 0.0 °C"HELADA"
These are evaluated independently and can fire alongside any temperature alert.
ThresholdAlert label
viento > 70.0 km/h"VIENTO_FUERTE"
lluvia > 30.0 mm"LLUVIA_INTENSA"
humedad >= 90 %"HUMEDAD_ALTA"
The method returns an empty list [] if validate_weather_data() fails or if any field cannot be converted to float.

RetryService

get_retry_session() is the sole export of services/retry_service.py. It wraps requests.Session with a urllib3.Retry strategy so that transient network failures against the AEMET API are handled automatically.

get_retry_session(retries, backoff_factor)

retries
number
default:"3"
Total number of retry attempts before giving up.
backoff_factor
number
default:"1.5"
Exponential backoff multiplier. With the default of 1.5, wait times between retries are approximately 1.5 s, 3 s, 4.5 s.
The strategy retries on HTTP status codes 429, 500, 502, 503, and 504, and only for GET requests. The configured adapter is mounted on both https:// and http:// prefixes.
import logging
from requests import Session
from urllib3.util.retry import Retry
from requests.adapters import HTTPAdapter

def get_retry_session(retries: int = 3, backoff_factor: float = 1.5) -> Session:
    session = Session()
    retry_strategy = Retry(
        total=retries,
        backoff_factor=backoff_factor,
        status_forcelist=[429, 500, 502, 503, 504],
        allowed_methods=["GET"]
    )
    adapter = HTTPAdapter(max_retries=retry_strategy)
    session.mount("https://", adapter)
    session.mount("http://", adapter)
    return session
Pass a custom retries value when testing against a slow or rate-limited staging endpoint. For production the defaults (3 retries, 1.5 s backoff) are appropriate for the AEMET open-data API.

Build docs developers (and LLMs) love