Dashboard analítico + estimador histórico de criminalidad para Londres (2008-2016).
No es un predictor del futuro. Es un sistema de análisis que estima perfiles históricos de crimen basados en datos reales.
Procesa ~3 millones de registros de crimen a nivel LSOA del dataset público de la Policía Metropolitana de Londres y los transforma en:
- Dashboard interactivo — visualizaciones por distrito, categoría, tendencia temporal, exportación a Excel.
- Pipeline DataOps/ETL — extracción desde BigQuery, limpieza, validación, agregación, snapshots, métricas y carga a Supabase.
- Estimador ML histórico — dado un distrito, categoría, mes y año, estima cuántos crímenes cabría esperar según el perfil histórico aprendido.
Importante: Este sistema NO predice el futuro. El modelo ML actual se entrena con un split aleatorio 70/30 sobre datos históricos, lo que puede introducir fuga temporal e inflar métricas. Para predicción real, ver Opciones de evolución.
| Capa | Tecnología | Configuración real |
|---|---|---|
| Frontend | React + Vite + MUI + Chart.js | apps/frontend, deploy en Vercel |
| Backend API | FastAPI + Uvicorn | apps/backend/api/predict.py, deploy en Render |
| Base de datos | Supabase/PostgreSQL | tabla london_crime_aggregated |
| DataOps | Python + pandas + BigQuery + SQLAlchemy | apps/backend/cli/pipeline_dataops.py |
| ML | scikit-learn | apps/backend/cli/ml_pipeline.py |
| Modelos | joblib | data/models/*.joblib |
| CI | GitHub Actions | .github/workflows/ci-backend.yml |
| Infra | Docker + Compose + nginx | Dockerfile, render.yaml, infra/ |
BigQuery: bigquery-public-data.london_crime.crime_by_lsoa
│
▼
apps/backend/cli/pipeline_dataops.py
│
├── pipeline/ingestion.py
├── pipeline/cleaning.py
├── pipeline/loading.py
├── pipeline/data_stage_manager.py
└── pipeline/metrics.py
│
├── data/processed/london_crime_aggregated.csv
├── Supabase: london_crime_aggregated
└── data/metrics/pipeline_metrics.jsonl
│
▼
apps/backend/cli/ml_pipeline.py
│
├── data/models/logistic_regression.joblib
├── data/models/crime_regressor.joblib
└── data/models/preprocessor.joblib
│
▼
apps/backend/api/predict.py ──→ Frontend React/Vercel
El modelo aprende el perfil histórico del dataset 2008-2016. Con los inputs borough, major_category, minor_category, year y month, devuelve:
- clasificación de incidencia alta/baja;
- probabilidad de alta/baja;
- estimación numérica de crímenes mensuales.
No extrapola de forma confiable fuera del rango histórico.
| Archivo | Rol |
|---|---|
apps/backend/cli/ml_pipeline.py |
Orquesta entrenamiento y guardado de modelos |
apps/backend/ml/preprocessing.py |
Carga CSV procesado, crea target y preprocessor sklearn |
apps/backend/ml/classification.py |
Entrena/evalúa LogisticRegression y RandomForestRegressor |
apps/backend/api/predict.py |
Carga modelos y sirve POST /predict |
data/models/logistic_regression.joblib |
Clasificador alta/baja |
data/models/crime_regressor.joblib |
Regresor de número de crímenes |
data/models/preprocessor.joblib |
Transformación de features para inferencia |
- Algoritmo:
LogisticRegression - Objetivo: clasificar incidencia alta (
1) o baja (0). - Target:
target_binary = total_crimes > median(total_crimes). - Features:
borough,major_category,minor_category,year,month_sin,month_cos. - Limitación: split aleatorio 70/30, no split temporal.
- Algoritmo:
RandomForestRegressor - Objetivo: estimar
predicted_crimes. - Mismas features que el clasificador.
- Salida API:
predicted_crimesredondeado ypredicted_crimes_rawsin redondear.
# Simplificado: split aleatorio, no temporal
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.3, random_state=42
)Esto significa que el modelo puede evaluar datos antiguos habiendo aprendido patrones de años posteriores. Las métricas describen desempeño histórico interno, no predicción futura real.
python -m apps.backend.cli.pipeline_dataopspython -m apps.backend.cli.pipeline_dataops --demo| Etapa | Módulo | Qué hace |
|---|---|---|
| Ingesta | pipeline/ingestion.py |
Lee BigQuery o genera muestra demo |
| Limpieza | pipeline/cleaning.py |
Estandariza columnas, valida tipos/rangos, elimina nulos/duplicados, normaliza texto |
| Validación | pipeline/data_stage_manager.py |
Guarda snapshots y reportes por etapa |
| Persistencia local | pipeline/loading.py |
Guarda CSV/Parquet en data/processed/ |
| Carga Supabase | pipeline/loading.py |
load_to_supabase() usa SUPABASE_DB_URL |
| Métricas | pipeline/metrics.py |
Exporta pipeline_metrics.jsonl |
| Variable | Uso |
|---|---|
SUPABASE_DB_URL |
Conexión PostgreSQL/Supabase para carga de datos |
GOOGLE_APPLICATION_CREDENTIALS |
Credenciales GCP para BigQuery |
BIGQUERY_ROW_LIMIT |
Límite de registros a leer |
SUPABASE_TABLE_NAME |
Tabla destino, por defecto london_crime_aggregated |
Render usa render.yaml:
services:
- type: web
name: datagestor-ml-api
env: docker
healthCheckPath: /healthLa imagen de producción se construye con el Dockerfile de la raíz. Ese Dockerfile instala requirements.txt, copia el proyecto, ejecuta/verifica el pipeline ML y levanta:
uvicorn apps.backend.api.predict:app --host 0.0.0.0 --port 8000Frontend React/Vite desde apps/frontend:
npm install
npm run buildVariables frontend:
| Variable | Uso |
|---|---|
VITE_SUPABASE_URL |
URL pública de Supabase |
VITE_SUPABASE_ANON_KEY |
anon key de Supabase |
VITE_ML_API_URL |
URL de FastAPI/Render |
Workflow real: .github/workflows/ci-backend.yml
Corre en push/PR a main o master:
- Python 3.11
pip install -r requirements.txtblack --check apps/backend/flake8 apps/backend/python -m pytest apps/backend/tests/ -v
| Archivo | Uso |
|---|---|
Dockerfile |
Producción API en Render |
infra/docker-compose.yml |
Backend + frontend local |
infra/backend.Dockerfile |
Contenedor backend/dev |
infra/frontend.Dockerfile |
Build React + nginx |
infra/nginx.conf |
SPA fallback + gzip + headers básicos |
docker compose -f infra/docker-compose.yml up --buildGET /healthRespuesta real:
{"status": "ok"}POST /predictRequest:
{
"borough": "westminster",
"major_category": "theft and handling",
"minor_category": "theft from shop",
"year": 2016,
"month": 6
}Response:
{
"prediction": 1,
"probability_high": 0.8234,
"probability_low": 0.1766,
"predicted_crimes": 42,
"predicted_crimes_raw": 42.37,
"threshold": 0.5,
"features_used": 120
}python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
uvicorn apps.backend.api.predict:app --reloadcd apps/frontend
npm install
npm run dev# Desde la raíz del repo
python -m apps.backend.cli.pipeline_dataops --demo
python -m apps.backend.cli.pipeline_dataops
python -m apps.backend.cli.ml_pipelineDataGestor/
├── apps/
│ ├── frontend/ # React + Vite
│ │ ├── src/App.jsx # Dashboard + estimador
│ │ └── public/ # métricas/logs para dashboard
│ └── backend/
│ ├── api/predict.py # FastAPI /health y /predict
│ ├── cli/ # entrypoints DataOps/ML
│ ├── pipeline/ # ingestion, cleaning, loading, metrics
│ ├── ml/ # preprocessing + modelos sklearn
│ └── tests/ # pytest
├── config/.env.example # variables ejemplo
├── data/
│ ├── processed/ # CSV/Parquet procesados
│ ├── metrics/ # métricas DataOps/ML
│ └── models/ # modelos joblib
├── infra/ # Docker Compose, Dockerfiles dev, nginx
├── .github/workflows/ci-backend.yml
├── render.yaml
├── Dockerfile
├── requirements.txt
├── docs/
└── README.md
Mantener el estimador histórico, documentando que no predice futuro.
Cambiar evaluación a entrenamiento histórico y test futuro:
train = df[df["year"] <= 2014]
test = df[df["year"] >= 2015]Añadir lag_1, lag_12, rolling averages y walk-forward validation.
Migrar a Prophet, SARIMA o LSTM para series temporales y predicción 2017+ por distrito/categoría.
docs/API.mddocs/ARCHITECTURE.mddocs/DEPLOYMENT.mddocs/ML_PIPELINE.mddocs/PRESENTATION.mdSECURITY.md
PYTHONPATH=. python -m pytest apps/backend/tests/ -vMIT