Files
trenes/FASE3-ANALYTICS.md
Millaguie 34c0cb50c7
Some checks failed
Auto Tag on Merge to Main / auto-tag (push) Successful in 27s
CI - Lint and Build / lint-backend (push) Failing after 30s
CI - Lint and Build / lint-frontend (push) Failing after 2s
CI - Lint and Build / build-frontend (push) Has been skipped
CI - Lint and Build / docker-build-test (push) Has been skipped
feat: Initial commit - Train tracking system
Complete real-time train tracking system for Spanish railways (Renfe/Cercanías):

- Backend API (Node.js/Express) with GTFS-RT polling workers
- Frontend dashboard (React/Vite) with Leaflet maps
- Real-time updates via Socket.io WebSocket
- PostgreSQL/PostGIS database with Flyway migrations
- Redis caching layer
- Docker Compose configuration for development and production
- Gitea CI/CD workflows (lint, auto-tag, release)
- Production deployment with nginx + Let's Encrypt SSL

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-28 00:21:15 +01:00

20 KiB

Fase 3: Analytics y Exploración Avanzada

Estado: IMPLEMENTADO (Backend)

La Fase 3 añade capacidades avanzadas de análisis, exploración de rutas, planificación de viajes y exportación de datos.


Características Implementadas

Backend

Base de Datos

  • Migración V6: Vistas materializadas para analytics
  • Funciones para heatmaps, estadísticas y análisis
  • Tablas para cache de exportaciones
  • Vistas de sistema: traffic_by_hour, traffic_by_route, daily_statistics, route_performance

Workers

  • Analytics Refresher: Refresco automático de vistas materializadas cada 15 minutos

API REST

  • Analytics API (/analytics) - Heatmaps, estadísticas, performance
  • Explorer API (/explorer) - Explorador de rutas, planificador de viajes, búsqueda

Frontend

  • Componentes de Analytics (pendiente)
  • Heatmap de tráfico (pendiente)
  • Dashboard de estadísticas (pendiente)
  • Planificador de viajes UI (pendiente)

📁 Nuevos Archivos Phase 3

Base de Datos

database/migrations/
└── V6__analytics_and_statistics.sql    # Vistas y funciones de analytics

Backend Workers

backend/src/worker/
└── analytics-refresher.js              # Refresco de vistas materializadas

Backend API

backend/src/api/routes/
├── analytics.js                        # Endpoints de analytics y exportación
└── explorer.js                         # Explorador de rutas y planificador

🚀 Ejecutar con Fase 3

Usando Docker Compose

# 1. Ejecutar migraciones (incluye V6)
make migrate

# 2. Iniciar todos los servicios (incluye analytics-refresher)
make start

# El worker analytics-refresher se inicia automáticamente
# y refresca las vistas cada 15 minutos

Desarrollo Local

cd backend

# Terminal 1: Analytics Refresher
npm run dev:analytics

# Terminal 2: API Server
npm run dev

📡 Nuevos Endpoints API

Analytics - Traffic

GET /analytics/traffic/heatmap

Obtener datos de heatmap de tráfico.

Query Parameters:

  • start_date (opcional): Fecha inicio (ISO 8601)
  • end_date (opcional): Fecha fin (ISO 8601)
  • grid_size (opcional): Tamaño de celda en grados (default: 0.1 ≈ 11km)

Ejemplo:

# Heatmap últimos 7 días
curl http://localhost:3000/analytics/traffic/heatmap

# Heatmap con grid más fino
curl http://localhost:3000/analytics/traffic/heatmap?grid_size=0.05

# Rango personalizado
curl "http://localhost:3000/analytics/traffic/heatmap?start_date=2025-11-20T00:00:00Z&end_date=2025-11-27T23:59:59Z"

Respuesta:

[
  {
    "lat": 40.4,
    "lon": -3.7,
    "intensity": 125,
    "avgSpeed": 78.5
  },
  {
    "lat": 41.3,
    "lon": 2.1,
    "intensity": 98,
    "avgSpeed": 65.3
  }
]

GET /analytics/traffic/hourly

Obtener patrón de tráfico por hora del día.

Query Parameters:

  • days (opcional): Número de días para analizar (default: 7)

Ejemplo:

curl http://localhost:3000/analytics/traffic/hourly?days=30

Respuesta:

[
  {
    "hour_of_day": 0,
    "avg_trains": 15.3,
    "avg_speed": 45.2,
    "total_observations": 45230
  },
  {
    "hour_of_day": 7,
    "avg_trains": 87.5,
    "avg_speed": 68.7,
    "total_observations": 125890
  }
]

GET /analytics/traffic/by-hour

Obtener estadísticas de tráfico agregadas por hora.

Query Parameters:

  • limit (opcional): Número de horas (default: 24)

Ejemplo:

curl http://localhost:3000/analytics/traffic/by-hour?limit=48

GET /analytics/traffic/by-route

Obtener estadísticas de tráfico por ruta.

Ejemplo:

curl http://localhost:3000/analytics/traffic/by-route

Respuesta:

[
  {
    "route_id": "AVE-MAD-BCN",
    "route_name": "Madrid - Barcelona",
    "route_type": "HIGH_SPEED",
    "total_trains": 234,
    "active_days": 30,
    "avg_speed": 185.4,
    "total_positions": 125890
  }
]

Analytics - Statistics

GET /analytics/statistics/daily

Obtener estadísticas diarias del sistema.

Query Parameters:

  • days (opcional): Número de días (default: 30)

Ejemplo:

curl http://localhost:3000/analytics/statistics/daily?days=90

Respuesta:

[
  {
    "date": "2025-11-27",
    "unique_trains": 456,
    "total_positions": 65432,
    "avg_speed": 72.3,
    "stopped_count": 12890,
    "moving_count": 52542
  }
]

GET /analytics/statistics/system

Obtener estado actual del sistema.

Ejemplo:

curl http://localhost:3000/analytics/statistics/system

Respuesta:

{
  "active_trains": 234,
  "active_alerts": 5,
  "delayed_trips": 12,
  "avg_delay_seconds": 450,
  "active_routes": 45,
  "last_update": "2025-11-27T15:30:00Z"
}

Analytics - Performance

GET /analytics/performance/routes

Obtener métricas de rendimiento de rutas (puntualidad, retrasos).

Query Parameters:

  • limit (opcional): Número de rutas (default: 20)

Ejemplo:

curl http://localhost:3000/analytics/performance/routes?limit=10

Respuesta:

[
  {
    "route_id": "AVE-MAD-BCN",
    "route_name": "Madrid - Barcelona",
    "total_trips": 345,
    "delayed_trips": 23,
    "on_time_trips": 322,
    "avg_delay_seconds": 180,
    "median_delay_seconds": 120,
    "max_delay_seconds": 1800,
    "punctuality_percentage": 93.33
  }
]

GET /analytics/performance/route/:routeId

Obtener estadísticas detalladas de una ruta específica.

Query Parameters:

  • days (opcional): Número de días para analizar (default: 7)

Ejemplo:

curl http://localhost:3000/analytics/performance/route/AVE-MAD-BCN?days=30

Respuesta:

{
  "total_trips": 234,
  "unique_trains": 45,
  "avg_speed": 185.4,
  "max_speed": 298.7,
  "total_distance_km": 98765.4,
  "avg_delay_seconds": 120,
  "on_time_percentage": 92.5
}

Analytics - Delays

GET /analytics/delays/top-routes

Obtener rutas con más retrasos.

Ejemplo:

curl http://localhost:3000/analytics/delays/top-routes

Respuesta:

[
  {
    "route_id": "MD-MAD-VAL",
    "route_name": "Madrid - Valencia",
    "delayed_count": 45,
    "avg_delay": 600,
    "max_delay": 2400
  }
]

Analytics - Stations

GET /analytics/stations/busiest

Obtener estaciones más transitadas.

Query Parameters:

  • limit (opcional): Número de estaciones (default: 20)

Ejemplo:

curl http://localhost:3000/analytics/stations/busiest?limit=10

Respuesta:

[
  {
    "stop_id": "MADRID-PUERTA-DE-ATOCHA",
    "stop_name": "Madrid Puerta de Atocha",
    "daily_trips": 456,
    "routes_count": 34
  }
]

GET /analytics/stations/:stationId/statistics

Obtener estadísticas de una estación específica.

Query Parameters:

  • days (opcional): Número de días (default: 7)

Ejemplo:

curl http://localhost:3000/analytics/stations/MADRID-PUERTA-DE-ATOCHA/statistics?days=30

Respuesta:

{
  "total_departures": 3450,
  "total_arrivals": 3420,
  "unique_routes": 45,
  "avg_delay_minutes": 2.5,
  "busiest_hour": 8
}

Analytics - Trains

GET /analytics/trains/:trainId/distance

Calcular distancia recorrida por un tren.

Query Parameters:

  • start_time (opcional): Timestamp inicio
  • end_time (opcional): Timestamp fin

Ejemplo:

curl "http://localhost:3000/analytics/trains/12345/distance?start_time=2025-11-27T00:00:00Z&end_time=2025-11-27T23:59:59Z"

Respuesta:

{
  "train_id": "12345",
  "start_time": "2025-11-27T00:00:00Z",
  "end_time": "2025-11-27T23:59:59Z",
  "distance_km": 1234.56
}

Analytics - Export

GET /analytics/export

Exportar datos en diferentes formatos.

Query Parameters:

  • table (requerido): Tabla a exportar
  • format (opcional): json, csv, geojson (default: json)
  • start_date (opcional): Fecha inicio
  • end_date (opcional): Fecha fin
  • limit (opcional): Límite de registros (default: 1000)

Tablas permitidas:

  • train_positions
  • trains
  • routes
  • stations
  • alerts
  • trip_updates
  • traffic_by_hour
  • daily_statistics

Ejemplo JSON:

curl "http://localhost:3000/analytics/export?table=trains&format=json"

Ejemplo CSV:

curl "http://localhost:3000/analytics/export?table=train_positions&format=csv&limit=5000" > positions.csv

Ejemplo GeoJSON:

curl "http://localhost:3000/analytics/export?table=stations&format=geojson" > stations.geojson

Analytics - Refresh

POST /analytics/refresh

Refrescar manualmente las vistas materializadas.

Ejemplo:

curl -X POST http://localhost:3000/analytics/refresh

Respuesta:

{
  "success": true,
  "message": "Analytics views refreshed successfully",
  "timestamp": "2025-11-27T15:30:00Z"
}

📍 Explorer - Route Explorer

GET /explorer/routes/:routeId

Obtener información completa de una ruta (trips, stops, shape).

Ejemplo:

curl http://localhost:3000/explorer/routes/AVE-MAD-BCN

Respuesta:

{
  "route": {
    "route_id": "AVE-MAD-BCN",
    "route_name": "Madrid - Barcelona",
    "route_type": "HIGH_SPEED"
  },
  "trips": [
    {
      "trip_id": "trip_001",
      "trip_headsign": "Barcelona Sants",
      "direction_id": 0
    }
  ],
  "stops": [
    {
      "stop_id": "MADRID-PUERTA-DE-ATOCHA",
      "stop_name": "Madrid Puerta de Atocha",
      "stop_lat": 40.4067,
      "stop_lon": -3.6906
    }
  ],
  "shape": {
    "shape_id": "shape_001",
    "points": [
      {
        "lat": 40.4067,
        "lon": -3.6906,
        "sequence": 1,
        "distance": 0
      }
    ]
  },
  "total_trips": 45,
  "total_stops": 8
}

GET /explorer/trips/:tripId/schedule

Obtener horario completo de un viaje.

Ejemplo:

curl http://localhost:3000/explorer/trips/trip_12345/schedule

Respuesta:

[
  {
    "stop_id": "MADRID-PUERTA-DE-ATOCHA",
    "stop_name": "Madrid Puerta de Atocha",
    "arrival_time": "08:00:00",
    "departure_time": "08:00:00",
    "stop_sequence": 1
  },
  {
    "stop_id": "BARCELONA-SANTS",
    "stop_name": "Barcelona Sants",
    "arrival_time": "10:45:00",
    "departure_time": "10:45:00",
    "stop_sequence": 3
  }
]

🚉 Explorer - Stations

GET /explorer/stations/:stationId

Obtener información completa de una estación.

Ejemplo:

curl http://localhost:3000/explorer/stations/MADRID-PUERTA-DE-ATOCHA

Respuesta:

{
  "station": {
    "stop_id": "MADRID-PUERTA-DE-ATOCHA",
    "stop_name": "Madrid Puerta de Atocha",
    "stop_lat": 40.4067,
    "stop_lon": -3.6906
  },
  "next_departures": [
    {
      "trip_id": "trip_001",
      "route_id": "AVE-MAD-BCN",
      "route_name": "Madrid - Barcelona",
      "headsign": "Barcelona Sants",
      "scheduled_departure": "15:00:00",
      "estimated_delay": 180,
      "status": "DELAYED"
    }
  ],
  "routes": [
    {
      "route_id": "AVE-MAD-BCN",
      "route_name": "Madrid - Barcelona"
    }
  ],
  "statistics": {
    "total_departures": 456,
    "total_arrivals": 450,
    "unique_routes": 34,
    "avg_delay_minutes": 2.5,
    "busiest_hour": 8
  }
}

GET /explorer/stations/:stationId/nearby

Obtener estaciones cercanas.

Query Parameters:

  • radius (opcional): Radio en km (default: 5)

Ejemplo:

curl "http://localhost:3000/explorer/stations/MADRID-PUERTA-DE-ATOCHA/nearby?radius=10"

Respuesta:

[
  {
    "stop_id": "MADRID-CHAMARTIN",
    "stop_name": "Madrid Chamartín",
    "stop_lat": 40.4728,
    "stop_lon": -3.6797,
    "distance_km": 7.8
  }
]

🗺️ Explorer - Trip Planner

GET /explorer/planner

Planificador de viajes entre dos estaciones.

Query Parameters:

  • origin (requerido): ID de estación origen
  • destination (requerido): ID de estación destino
  • time (opcional): Hora de salida (HH:MM:SS)
  • date (opcional): Fecha (YYYY-MM-DD)

Ejemplo:

curl "http://localhost:3000/explorer/planner?origin=MADRID-PUERTA-DE-ATOCHA&destination=BARCELONA-SANTS&time=08:00:00"

Respuesta:

{
  "origin": "MADRID-PUERTA-DE-ATOCHA",
  "destination": "BARCELONA-SANTS",
  "requested_time": "08:00:00",
  "requested_date": "today",
  "direct_trips": [
    {
      "trip_id": "trip_001",
      "route_id": "AVE-MAD-BCN",
      "route_name": "Madrid - Barcelona",
      "trip_headsign": "Barcelona Sants",
      "origin_departure": "08:00:00",
      "destination_arrival": "10:45:00",
      "duration_minutes": 165,
      "delay": {
        "delay_seconds": 180,
        "schedule_relationship": "SCHEDULED"
      }
    }
  ],
  "trips_with_transfer": [
    {
      "trip1_id": "trip_002",
      "route1_name": "Madrid - Zaragoza",
      "trip2_id": "trip_003",
      "route2_name": "Zaragoza - Barcelona",
      "origin_departure": "08:30:00",
      "transfer_arrival": "09:50:00",
      "transfer_departure": "10:10:00",
      "destination_arrival": "11:30:00",
      "transfer_station": "ZARAGOZA-DELICIAS",
      "transfer_station_name": "Zaragoza Delicias",
      "total_duration_minutes": 180
    }
  ],
  "total_options": 3
}

GET /explorer/routes/between

Encontrar rutas que conectan dos estaciones.

Query Parameters:

  • origin (requerido): ID de estación origen
  • destination (requerido): ID de estación destino

Ejemplo:

curl "http://localhost:3000/explorer/routes/between?origin=MADRID-PUERTA-DE-ATOCHA&destination=BARCELONA-SANTS"

Respuesta:

[
  {
    "route_id": "AVE-MAD-BCN",
    "route_name": "Madrid - Barcelona",
    "route_type": "HIGH_SPEED",
    "route_color": "FF6600",
    "daily_trips": 25
  }
]

GET /explorer/search

Buscar estaciones por nombre.

Query Parameters:

  • query (requerido): Término de búsqueda (mínimo 2 caracteres)
  • limit (opcional): Número de resultados (default: 10)

Ejemplo:

curl "http://localhost:3000/explorer/search?query=madrid&limit=5"

Respuesta:

[
  {
    "stop_id": "MADRID-PUERTA-DE-ATOCHA",
    "stop_name": "Madrid Puerta de Atocha",
    "stop_lat": 40.4067,
    "stop_lon": -3.6906,
    "location_type": 1,
    "parent_station": null
  },
  {
    "stop_id": "MADRID-CHAMARTIN",
    "stop_name": "Madrid Chamartín",
    "stop_lat": 40.4728,
    "stop_lon": -3.6797,
    "location_type": 1,
    "parent_station": null
  }
]

🗄️ Vistas Materializadas

traffic_by_hour

Tráfico agregado por hora (últimos 30 días).

Columnas:

  • hour: Hora (timestamp)
  • active_trains: Número de trenes activos
  • total_positions: Total de posiciones registradas
  • avg_speed: Velocidad promedio
  • median_speed: Velocidad mediana
  • max_speed: Velocidad máxima

traffic_by_route

Tráfico por ruta (últimos 30 días).

Columnas:

  • route_id, route_name, route_type
  • total_trains: Total de trenes diferentes
  • active_days: Días con actividad
  • avg_speed: Velocidad promedio
  • total_positions: Posiciones registradas

daily_statistics

Estadísticas diarias del sistema (últimos 90 días).

Columnas:

  • date: Fecha
  • unique_trains: Trenes únicos
  • total_positions: Posiciones totales
  • avg_speed: Velocidad promedio
  • stopped_count: Posiciones paradas
  • moving_count: Posiciones en movimiento

route_performance

Rendimiento y puntualidad por ruta (últimos 30 días).

Columnas:

  • route_id, route_name
  • total_trips: Total de viajes
  • delayed_trips: Viajes retrasados
  • on_time_trips: Viajes puntuales
  • avg_delay_seconds: Retraso promedio
  • median_delay_seconds: Retraso mediano
  • max_delay_seconds: Retraso máximo
  • punctuality_percentage: % de puntualidad

Refresco: Cada 15 minutos automáticamente via analytics-refresher worker.


🧪 Probar Phase 3

1. Verificar Analytics Refresher

# Ver logs del worker
docker-compose logs -f analytics-refresher

# Deberías ver cada 15 minutos:
# "Starting analytics views refresh..."
# "Analytics views refreshed successfully"

2. Probar Analytics API

# Sistema general
curl http://localhost:3000/analytics/statistics/system | jq

# Heatmap
curl http://localhost:3000/analytics/traffic/heatmap | jq

# Performance de rutas
curl http://localhost:3000/analytics/performance/routes | jq

# Rutas más retrasadas
curl http://localhost:3000/analytics/delays/top-routes | jq

# Estaciones más transitadas
curl http://localhost:3000/analytics/stations/busiest | jq

3. Probar Explorer API

# Buscar estación
curl "http://localhost:3000/explorer/search?query=madrid" | jq

# Info de estación
curl http://localhost:3000/explorer/stations/MADRID-PUERTA-DE-ATOCHA | jq

# Info de ruta
curl http://localhost:3000/explorer/routes/AVE-MAD-BCN | jq

# Planificar viaje
curl "http://localhost:3000/explorer/planner?origin=MADRID-PUERTA-DE-ATOCHA&destination=BARCELONA-SANTS" | jq

4. Probar Exportación

# Exportar a CSV
curl "http://localhost:3000/analytics/export?table=routes&format=csv" > routes.csv

# Exportar a GeoJSON
curl "http://localhost:3000/analytics/export?table=stations&format=geojson" > stations.geojson

# Exportar posiciones filtradas
curl "http://localhost:3000/analytics/export?table=train_positions&start_date=2025-11-27T00:00:00Z&limit=1000" > positions.json

📊 Use Cases

Dashboard de Control

# Obtener datos para dashboard
curl http://localhost:3000/analytics/statistics/system
curl http://localhost:3000/analytics/traffic/by-route
curl http://localhost:3000/analytics/delays/top-routes
curl http://localhost:3000/analytics/stations/busiest?limit=5

Análisis de Rendimiento

# Análisis de una ruta específica
curl http://localhost:3000/analytics/performance/route/AVE-MAD-BCN?days=30

# Estadísticas diarias últimos 30 días
curl http://localhost:3000/analytics/statistics/daily?days=30

# Distancia recorrida por tren
curl "http://localhost:3000/analytics/trains/12345/distance?start_time=2025-11-01T00:00:00Z"

Planificación de Viajes

# Buscar estación
curl "http://localhost:3000/explorer/search?query=barcelona"

# Ver info completa de estación
curl http://localhost:3000/explorer/stations/BARCELONA-SANTS

# Planificar viaje
curl "http://localhost:3000/explorer/planner?origin=MADRID-PUERTA-DE-ATOCHA&destination=BARCELONA-SANTS&time=08:00:00"

# Ver rutas que conectan dos puntos
curl "http://localhost:3000/explorer/routes/between?origin=MADRID-PUERTA-DE-ATOCHA&destination=BARCELONA-SANTS"

🐛 Troubleshooting Phase 3

Las vistas materializadas están vacías

Causa: No hay suficientes datos históricos o no se han refrescado.

Solución:

# Refrescar manualmente
curl -X POST http://localhost:3000/analytics/refresh

# Verificar en PostgreSQL
make psql
> SELECT COUNT(*) FROM traffic_by_hour;
> SELECT COUNT(*) FROM traffic_by_route;

El planificador no encuentra viajes

Causa: Datos GTFS Static no cargados o formato de parámetros incorrecto.

Solución:

# Verificar que hay trips y stop_times
make psql
> SELECT COUNT(*) FROM trips;
> SELECT COUNT(*) FROM stop_times;

# Verificar formato de parámetros
curl "http://localhost:3000/explorer/planner?origin=STOP_ID_ORIGIN&destination=STOP_ID_DEST"

Export devuelve error

Causa: Tabla no permitida o parámetros inválidos.

Solución:

# Ver tablas permitidas
curl "http://localhost:3000/analytics/export" | jq '.allowed_tables'

# Usar tabla válida
curl "http://localhost:3000/analytics/export?table=routes&format=json"

📝 Próximos Pasos

Funcionalidades pendientes de Phase 3:

  • Frontend: Heatmap de tráfico en mapa
  • Frontend: Dashboard de estadísticas con gráficos
  • Frontend: UI del planificador de viajes
  • Frontend: Explorador de rutas interactivo
  • WebSocket para updates de analytics en tiempo real
  • Overlay de infraestructura ADIF (WMS)
  • Predicciones ML (Fase 4)

📚 Documentación Relacionada


Estado: Fase 3 - Backend Completo, Frontend Pendiente Fecha: 27 noviembre 2025 Próxima Fase: Frontend Phase 3 + Fase 4 (ML y Predicciones)