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 handles two distinct data paths: a live path that pulls real-time observations from AEMET using the device’s GPS coordinates, and a manual path that accepts user-submitted readings through a web form. Both paths converge on the same normalized data format before returning a response or writing to storage.

Live weather data flow

1

Browser requests GPS permission

The dashboard page prompts the user to share their location. The browser’s Geolocation API returns a latitude and longitude pair.
2

Coordinates sent to GET /api/clima

The frontend sends a request to GET /api/clima?lat=X&lon=Y. The api_clima() view in app.py reads the lat and lon query parameters and validates that both are present before proceeding.
3

WeatherAPIService fetches the AEMET metadata URL

WeatherAPIService._obtener_datos_crudos() sends an authenticated GET request to:
https://opendata.aemet.es/opendata/api/observacion/convencional/todas
The api_key is read from the AEMET_API_KEY environment variable. This first response does not contain observation data — it returns a JSON object with a datos key pointing to a second URL.
4

Second request fetches the actual observation data

WeatherAPIService extracts the datos URL from the first response and makes a second GET request to retrieve the full list of station observations as a JSON array.
AEMET uses a two-step pattern for all its data endpoints: the first call returns a redirect URL, and the second call retrieves the actual payload. Both requests reuse the same retry-enabled requests.Session.
5

calcular_distancia() finds the nearest station

WeatherAPIService.obtener_clima_por_coordenadas() iterates over every observation in the array. For each station it calls calcular_distancia() from utils/helpers.py, which applies the Haversine formula to compute the great-circle distance (in kilometres) between the user’s coordinates and the station’s lat/lon fields. The station with the smallest distance is selected.
6

normalizar_datos_aemet() maps raw fields to standard names

The raw station observation is passed to normalizar_datos_aemet(). It maps AEMET’s abbreviated field names to readable keys:
Raw AEMET fieldNormalized keyDescription
ubiestacionStation name
fintfechaObservation timestamp
tatemperaturaAir temperature (°C)
hrhumedadRelative humidity (%)
vvvientoWind speed (km/h)
prespresionAtmospheric pressure (hPa)
preclluviaPrecipitation (mm)
Missing fields default to 0. All values are cast to float.
7

AlertService.evaluar_alertas() appends alert labels

Before returning, normalizar_datos_aemet() calls alert_service.evaluar_alertas() with the normalized dictionary. The method checks each reading against fixed thresholds and returns a list of active alert strings (for example ["NARANJA", "VIENTO_FUERTE"]). The list is added to the dictionary under the alertas key.
8

Normalized JSON returned to the browser

app.py serializes the final dictionary with jsonify() and returns it as an HTTP 200 response. The frontend renders the station name, readings, and any alert badges.

Manual data flow

1

User fills the form on /registro

The user enters station ID, date, temperature, humidity, wind speed, and rainfall into the form rendered by manual_controller.
2

POST /api/registrar receives the JSON payload

manual_controller reads the submitted values from the request body. The route accepts POST requests to /api/registrar.
3

validate_weather_data() checks all fields

Before any storage operation, validate_weather_data() from utils/validators.py verifies that all required fields are present and that numeric values are in acceptable ranges. An invalid payload returns an error response immediately.
4

RegistroClimatico model is created

A RegistroClimatico instance is constructed with the validated values. The model coerces all numeric fields to float and exposes a to_dict() method for serialization.
5

JSONRepository.guardar() appends the record

JSONRepository.guardar() reads the current contents of data/registros_climaticos.json, appends the new record dictionary (via RegistroClimatico.to_dict()), and writes the updated list back to disk with UTF-8 encoding and 4-space indentation. If the file does not exist yet, _ensure_file_exists() creates it with an empty array on first use.

Retry logic

All outbound HTTP calls made by WeatherAPIService go through a session created by get_retry_session() in services/retry_service.py. The session mounts a urllib3.util.retry.Retry adapter on both https:// and http:// prefixes with the following configuration:
SettingValue
Total retries3
Backoff factor1.5 (waits 1.5 s, 3 s, 4.5 s between attempts)
Retried status codes429, 500, 502, 503, 504
Allowed methodsGET only
The backoff factor means the delay between attempt n and attempt n+1 is backoff_factor × (2 ^ (n - 1)) seconds. With a factor of 1.5, the waits are 1.5 s, 3 s, and 4.5 s before the request is considered permanently failed.

Build docs developers (and LLMs) love