Layer diagram
app.py, travel down through the service layer for processing, optionally reach the repository to read or write stored records, and return a response back up the stack.
Component breakdown
Controllers
view_controller — renders the main dashboard, historical records list, API demo page, and the filter/query (
/consulta) form via view_bp.manual_controller — handles the manual data-entry form at /registro and the POST /api/registrar endpoint via manual_bp.compare_controller — orchestrates the side-by-side comparison between a stored manual record and the live AEMET reading for the same station and date.Services
WeatherAPIService — fetches all conventional station observations from AEMET using a two-step HTTP pattern and locates the nearest station via Haversine distance.NormalizerService — maps raw AEMET field names (
ta, hr, vv, pres, prec) to standard keys and calls AlertService to append alert labels.AlertService — evaluates normalized weather data against fixed thresholds and returns a list of active alert strings.RetryService — provides a requests.Session pre-configured with exponential back-off and automatic retries on transient HTTP errors.Repositories
JSONRepository — reads and writes
data/registros_climaticos.json. It exposes guardar() to append a record, filtrar_registros() to query by municipality, date, or source, and obtener_ultimo_registro() to retrieve the most recent entry for a given station.Models
RegistroClimatico — holds a single weather observation (station ID, date, temperature, humidity, wind, and rain). All numeric fields are coerced to
float on construction, and to_dict() serializes it for storage or JSON responses.Zona — represents the link between a municipality and its AEMET reference station. Holds municipio, cod_ine, id_estacion, and estacion_referencia. Created from a JSON dict via Zona.from_dict() using entries from estacion_por_municipio.json.Routes
Tech stack
Blueprint registration
view_bp and manual_bp are Flask Blueprints registered in app.py with app.register_blueprint(). This keeps each controller’s routes, templates, and helpers self-contained. The /comparar and /api/clima routes are defined directly on the app object because they orchestrate logic from multiple services and do not belong to a single controller domain.
A
context_processor registered in app.py automatically injects breadcrumb data (breadcrumbs) into every template, so individual controllers do not need to build navigation trails themselves.