oraculo_api / README.md
Diiegoal's picture
Actualizo la app
3e950b6
|
Raw
History Blame Contribute Delete
30.9 kB
---
title: Oraculo Adult Income API
emoji: 🧠
colorFrom: green
colorTo: blue
sdk: docker
app_port: 7860
base_path: /docs
pinned: false
---
# 🧠 Oráculo Adult Income API
<p align="center">
<img alt="Python" src="https://img.shields.io/badge/Python-3.11+-3776AB?logo=python&logoColor=white">
<img alt="FastAPI" src="https://img.shields.io/badge/FastAPI-API%20REST-009688?logo=fastapi&logoColor=white">
<img alt="SQLAlchemy" src="https://img.shields.io/badge/SQLAlchemy-2.0-red?logo=sqlalchemy&logoColor=white">
<img alt="Alembic" src="https://img.shields.io/badge/Alembic-Migrations-4B5563">
<img alt="JWT" src="https://img.shields.io/badge/Auth-JWT-black">
<img alt="LightGBM" src="https://img.shields.io/badge/Model-LightGBM-7CB342">
<img alt="Docker" src="https://img.shields.io/badge/Deploy-Docker-2496ED?logo=docker&logoColor=white">
</p>
<p align="center">
API REST para inferencia del problema <strong>Adult Census Income</strong>, con autenticación, persistencia, trazabilidad, validaciones estrictas,
middlewares de seguridad y una capa de compatibilidad entre el notebook de entrenamiento y el backend de producción.
</p>
---
## ✨ Qué es este proyecto
`oraculo_api` es el backend de inferencia estructurada del ecosistema Oráculo.
Su responsabilidad no es solamente “cargar un `.pkl` y responder una predicción”, sino construir una frontera sólida entre:
- el trabajo analítico hecho en notebook,
- el artefacto serializado del modelo,
- el contrato HTTP consumido por otras capas,
- la autenticación de usuarios,
- y la trazabilidad persistente de cada inferencia.
En términos prácticos, este servicio permite:
- registrar usuarios;
- autenticar con JWT;
- validar payloads del dataset Adult Income;
- ejecutar predicciones sobre el pipeline cargado;
- guardar historial por usuario;
- consultar predicciones anteriores;
- verificar salud de la API, del modelo y de la base de datos;
- desplegar localmente, en Docker, Render o Hugging Face Spaces.
> ⚠️ Además, el backend incluye una capa de compatibilidad para artefactos exportados desde notebook. Si el `.pkl` no serializa todas las recetas de feature engineering, la clase `PipelineProduccionMLOps` puede reconstruir parte de esas reglas a partir de artefactos y del dataset de referencia `adult.csv`.
---
## 🎯 Objetivo del backend
Esta API está diseñada para resolver cuatro necesidades del proyecto:
1. **Exponer inferencia de modelo como servicio HTTP estable y autenticado.**
2. **Blindar el salto notebook → backend**, evitando que el modelo quede atrapado en un entorno puramente exploratorio.
3. **Persistir evidencia operativa** de cada solicitud de predicción: payload, versión del modelo, request id, latencia y usuario.
4. **Servir como capa fuente de verdad** para otras aplicaciones del ecosistema, como el agente IA y la interfaz web.
---
## 🧱 Stack tecnológico
### Backend y servidor
- **FastAPI**: framework principal de la API.
- **Uvicorn**: servidor ASGI.
- **Starlette middlewares**: capa de compresión, hosts confiables y middleware base.
- **python-multipart**: soporte de cuerpos multipart cuando sea necesario.
### Configuración y seguridad
- **Pydantic v2**: validaciones, contratos de entrada y salida.
- **pydantic-settings**: configuración por variables de entorno.
- **python-dotenv**: soporte para `.env` local.
- **bcrypt**: hashing de contraseñas.
- **PyJWT**: creación y validación de tokens JWT.
### Persistencia y migraciones
- **SQLAlchemy 2.0**: ORM y acceso a base de datos.
- **Alembic**: migraciones versionadas.
- **SQLite** por defecto, con soporte para cambiar a **PostgreSQL** vía `DATABASE_URL`.
### ML y datos
- **joblib**: carga del artefacto serializado.
- **LightGBM**: modelo principal del pipeline.
- **numpy**, **pandas**, **scikit-learn**, **scipy**: base del pipeline tabular y de las transformaciones.
### Testing
- **pytest**: framework de pruebas.
- **httpx / TestClient**: validación de endpoints y contrato HTTP.
- **pytest-asyncio**: soporte adicional para contextos asíncronos de prueba.
---
## 📦 Dependencias exactas (`requirements.txt`)
### API / Web
- `fastapi==0.135.3`
- `uvicorn==0.44.0`
- `httptools==0.7.1`
- `watchfiles==1.1.1`
- `websockets==16.0`
- `python-multipart==0.0.26`
### Configuración / Seguridad
- `bcrypt==5.0.0`
- `PyJWT==2.12.1`
- `pydantic==2.12.5`
- `pydantic-settings==2.13.1`
- `python-dotenv==1.2.2`
### Base de datos / ORM / Migraciones
- `SQLAlchemy==2.0.49`
- `alembic==1.18.4`
### Machine Learning / Data
- `joblib==1.5.3`
- `lightgbm==4.6.0`
- `numpy==2.4.4`
- `pandas==3.0.2`
- `scikit-learn==1.8.0`
- `scipy==1.17.1`
### Testing
- `httpx==0.28.1`
- `pytest==9.0.3`
- `pytest-asyncio==1.3.0`
---
## 🗂️ Estructura real del proyecto
```text
oraculo_api/
├── .dockerignore
├── .env.example
├── Dockerfile
├── README.md
├── requirements.txt
├── alembic.ini
├── adult.csv
├── compas-scores-raw.csv
├── EDA_For_All_Tree_clean.ipynb
├── oraculo.db
├── alembic/
│ └── env.py
├── app/
│ ├── main.py
│ ├── api/
│ │ ├── dependencies.py
│ │ ├── router.py
│ │ └── v1/
│ │ ├── router.py
│ │ └── endpoints/
│ │ ├── auth.py
│ │ ├── health.py
│ │ └── predictions.py
│ ├── core/
│ │ ├── config.py
│ │ ├── error_handlers.py
│ │ ├── exceptions.py
│ │ ├── logging.py
│ │ ├── middleware.py
│ │ └── security.py
│ ├── db/
│ │ ├── __init__.py
│ │ ├── base.py
│ │ ├── session.py
│ │ ├── seeds.py
│ │ ├── models/
│ │ │ ├── __init__.py
│ │ │ ├── user.py
│ │ │ └── prediction_log.py
│ │ └── repositories/
│ │ ├── __init__.py
│ │ ├── users.py
│ │ └── predictions.py
│ ├── ml/
│ │ ├── custom_transformers.py
│ │ ├── model_manager.py
│ │ └── pipeline_produccion.pkl
│ ├── schemas/
│ │ ├── auth.py
│ │ ├── common.py
│ │ ├── health.py
│ │ └── prediction.py
│ └── services/
│ ├── __init__.py
│ ├── auth.py
│ ├── health.py
│ └── prediction.py
└── tests/
├── __init__.py
├── conftest.py
└── api/
├── test_auth_api.py
├── test_health_api.py
└── test_prediction_api.py
```
---
## 🧭 Arquitectura por capas
```mermaid
flowchart TD
A[Cliente / Swagger / Web / Agente] --> B[FastAPI Routers]
B --> C[Dependencies]
C --> D[Services]
D --> E1[Repositories / SQLAlchemy]
D --> E2[ModelManager / PipelineProduccionMLOps]
E1 --> F[(SQLite / PostgreSQL)]
E2 --> G[pipeline_produccion.pkl]
E2 --> H[model_manifest.json]
E2 --> I[adult.csv]
```
### Flujo interno de una predicción
1. El cliente envía un `POST /api/v1/predictions`.
2. FastAPI valida el payload con `PredictionInput`.
3. `get_current_user` exige un JWT válido.
4. `PredictionService` transforma el payload en dos versiones:
- **input payload** con aliases del contrato HTTP;
- **normalized payload** con nombres internos Python-friendly.
5. `ModelManager.predict_one()` ejecuta el pipeline cargado.
6. Se calcula la latencia.
7. `PredictionRepository.create()` persiste el evento completo en `prediction_logs`.
8. La respuesta vuelve con `id`, `prediction`, `probability`, `request_id`, `model_version` y payloads trazables.
---
## 🧠 Qué hace cada capa
### `app/main.py`
Es el punto de entrada de la aplicación.
Responsabilidades:
- crear la instancia FastAPI;
- configurar logging;
- inicializar `engine` y `session_factory`;
- crear tablas automáticamente si está habilitado;
- seedear un administrador inicial;
- cargar el modelo al arrancar;
- instalar middlewares;
- registrar handlers globales de error;
- exponer el endpoint raíz `/`.
### `app/api/`
Contiene la superficie HTTP del sistema.
- `router.py`: compone el router principal.
- `v1/router.py`: agrupa los endpoints bajo `/api/v1`.
- `v1/endpoints/auth.py`: registro, login y `me`.
- `v1/endpoints/health.py`: salud `live` y `ready`.
- `v1/endpoints/predictions.py`: creación, listado y consulta de predicciones.
### `app/api/dependencies.py`
Resuelve dependencias reutilizables de FastAPI:
- settings actuales;
- model manager cargado en `app.state`;
- sesiones de base de datos;
- repositorios;
- servicios;
- usuario autenticado;
- usuario administrador.
### `app/core/`
Es la capa transversal del backend.
- `config.py`: define `Settings`, lee variables de entorno y normaliza listas como `ALLOWED_HOSTS` y `CORS_ALLOW_ORIGINS`.
- `security.py`: hashing, verificación de passwords, creación y decodificación de JWT, extracción del bearer token.
- `middleware.py`: request id, client ip, headers de seguridad, límite de tamaño y rate limiting.
- `exceptions.py`: errores tipados de dominio y operación.
- `error_handlers.py`: respuestas JSON consistentes para errores esperados e inesperados.
- `logging.py`: configura logging estructurado a nivel global.
### `app/db/`
Contiene persistencia y modelo relacional.
- `base.py`: `DeclarativeBase`, naming convention y `TimestampMixin`.
- `session.py`: engine, factory de sesiones, chequeo de conexión y manejo transaccional.
- `models/user.py`: entidad `users`.
- `models/prediction_log.py`: entidad `prediction_logs`.
- `repositories/users.py`: consultas y creación de usuarios.
- `repositories/predictions.py`: creación, listado filtrado y consulta de predicciones por usuario.
- `seeds.py`: creación opcional del admin bootstrap.
### `app/services/`
Aquí está la lógica de negocio, separada del transporte HTTP.
- `AuthService`: registro, login y recuperación de usuario.
- `HealthService`: estado de vida y readiness real.
- `PredictionService`: inferencia, hashing de payload, persistencia y respuestas paginadas.
### `app/ml/`
Es la capa de integración con el artefacto del modelo.
- `model_manager.py`: carga el `.pkl`, lee `model_manifest.json`, verifica estado y expone `predict`, `predict_proba` y `predict_one`.
- `custom_transformers.py`: define `PipelineProduccionMLOps`, que actúa como puente de compatibilidad entre el notebook exportado y el backend.
### `app/schemas/`
Modelos Pydantic del contrato público.
- `auth.py`: DTOs de registro, login, token y usuario.
- `health.py`: respuestas de health.
- `prediction.py`: payload de entrada, detalle de predicción y lista paginada.
- `common.py`: base schema y metadatos de paginación.
### `tests/`
La suite prueba el contrato HTTP desacoplándolo del modelo real cuando conviene.
- `conftest.py` crea `FakeModelManager`, app de prueba, token, headers y payload válido.
- `test_auth_api.py` valida registro, conflicto, login y `me`.
- `test_health_api.py` valida raíz, `live` y `ready`.
- `test_prediction_api.py` valida autenticación, creación, filtros, aislamiento por usuario, payload grande, errores y recuperación por id.
---
## 🔐 Seguridad implementada
### Autenticación
- Login con email y password.
- Emisión de **JWT** firmado con `HS256` por defecto.
- Expiración configurable (`ORACULO_ACCESS_TOKEN_EXPIRE_MINUTES`).
- Endpoint `/api/v1/auth/me` para resolver el usuario autenticado.
### Contraseñas
- Hash con **bcrypt**.
- Si la contraseña supera el límite interno de bcrypt (72 bytes), el código hace **pre-hashing SHA-256** antes de `bcrypt.hashpw`, evitando truncamientos silenciosos.
### Validación de entrada
- `extra="forbid"` en esquemas sensibles para bloquear campos sorpresa.
- Validaciones de longitud, tipo y rango.
- Passwords fuertes obligatorias en registro:
- mayúscula,
- minúscula,
- número,
- carácter especial,
- longitud mínima de 12.
### Middlewares defensivos
- **TrustedHostMiddleware** para hosts permitidos.
- **GZipMiddleware** para compresión de respuestas.
- **SecurityHeadersMiddleware** con:
- `X-Content-Type-Options: nosniff`
- `Referrer-Policy: no-referrer`
- `Permissions-Policy`
- `Cache-Control: no-store`
- `Pragma: no-cache`
- `Content-Security-Policy`
- **MaxRequestSizeMiddleware** para rechazar payloads demasiado grandes.
- **RateLimitMiddleware** in-memory por IP.
- **RequestContextMiddleware** para generar `X-Request-ID` y `X-Process-Time-MS`.
### Errores controlados
Todos los errores retornan JSON consistente bajo la forma:
```json
{
"error": {
"code": "validation_error",
"message": "Request validation failed.",
"detail": {},
"request_id": "..."
}
}
```
---
## 🗃️ Modelo de datos
### Tabla `users`
Campos principales:
- `id`
- `email`
- `full_name`
- `password_hash`
- `role`
- `is_active`
- `created_at`
- `updated_at`
### Tabla `prediction_logs`
Campos principales:
- `id`
- `user_id`
- `request_id`
- `ip_address`
- `label`
- `probability`
- `latency_ms`
- `model_version`
- `payload_hash`
- `input_payload`
- `normalized_payload`
- `notes`
- `created_at`
- `updated_at`
Esto convierte cada inferencia en un evento auditable y recuperable.
---
## 🤖 Integración con el modelo
### `ModelManager`
El `ModelManager` es el punto central de carga e inferencia. Se encarga de:
- cargar el artefacto `pipeline_produccion.pkl`;
- registrar una clase puente en `__main__` para que `joblib` pueda deserializar objetos definidos originalmente en notebook;
- intentar leer `model_manifest.json` si existe;
- exponer `predict`, `predict_proba` y `predict_one`;
- encapsular fallas como `ServiceUnavailableError` o `ModelInferenceError`.
### `PipelineProduccionMLOps`
Esta clase existe para hacer el artefacto más robusto en producción.
Responsabilidades principales:
- normalizar nombres de columnas (`snake_case`, puntos, caracteres raros);
- limpiar texto categórico;
- reconstruir recetas faltantes si el notebook no serializó todo correctamente;
- aplicar rare labeling;
- aplicar target encoding y mapeos binarios;
- reconstruir fórmulas derivadas;
- generar ratios matemáticos;
- aplicar winsorización;
- reusar escaladores si existen;
- eliminar columnas de fuga o basura si están declaradas;
- realinear el DataFrame final con las features esperadas por el modelo entrenado.
### Artefactos esperados
El backend espera, como mínimo:
- `app/ml/pipeline_produccion.pkl`
Y opcionalmente:
- `app/ml/model_manifest.json`
Adicionalmente, puede usar:
- `adult.csv` como dataset de referencia para recomponer recetas faltantes de ingeniería de features.
---
## 🌱 Variables de entorno
Crea un archivo `.env` local a partir de `.env.example`.
### Variables principales
| Variable | Descripción |
| ------------------------------------- | ------------------------------------------------------------------ |
| `ORACULO_APP_NAME` | Nombre público del servicio. |
| `ORACULO_APP_VERSION` | Versión de la API. |
| `ORACULO_ENVIRONMENT` | Entorno (`local`, `development`, `test`, `staging`, `production`). |
| `ORACULO_DEBUG` | Activa modo debug. |
| `ORACULO_DATABASE_URL` | URL de conexión a BD. Por defecto SQLite local. |
| `ORACULO_DATABASE_ECHO` | Log SQL de SQLAlchemy. |
| `ORACULO_AUTO_CREATE_TABLES` | Crea tablas automáticamente al arrancar. |
| `ORACULO_AUTO_SEED_ADMIN` | Activa siembra automática de admin. |
| `ORACULO_SEED_ADMIN_EMAIL` | Email del admin bootstrap. |
| `ORACULO_SEED_ADMIN_PASSWORD` | Password del admin bootstrap. |
| `ORACULO_SEED_ADMIN_NAME` | Nombre visible del admin bootstrap. |
| `ORACULO_MODEL_PATH` | Ruta al `.pkl` de producción. |
| `ORACULO_JWT_SECRET_KEY` | Clave secreta JWT. |
| `ORACULO_JWT_ALGORITHM` | Algoritmo JWT. |
| `ORACULO_ACCESS_TOKEN_EXPIRE_MINUTES` | Duración del token. |
| `ORACULO_ALLOWED_HOSTS` | Hosts permitidos. |
| `ORACULO_CORS_ALLOW_ORIGINS` | Orígenes CORS permitidos. |
| `ORACULO_MAX_REQUEST_SIZE_BYTES` | Tamaño máximo del body. |
| `ORACULO_RATE_LIMIT_ENABLED` | Activa rate limit. |
| `ORACULO_RATE_LIMIT_REQUESTS` | Número máximo de requests por ventana. |
| `ORACULO_RATE_LIMIT_WINDOW_SECONDS` | Duración de la ventana. |
| `ORACULO_DOCS_ENABLED` | Activa/desactiva `/docs`, `/redoc` y `/openapi.json`. |
### Ejemplo `.env`
```env
ORACULO_APP_NAME=Oraculo Adult Income API
ORACULO_APP_VERSION=2.0.0
ORACULO_ENVIRONMENT=development
ORACULO_DEBUG=false
ORACULO_DATABASE_URL=sqlite:///./oraculo.db
ORACULO_DATABASE_ECHO=false
ORACULO_AUTO_CREATE_TABLES=true
ORACULO_AUTO_SEED_ADMIN=true
ORACULO_SEED_ADMIN_EMAIL=admin@example.com
ORACULO_SEED_ADMIN_PASSWORD=ChangeMe!12345
ORACULO_SEED_ADMIN_NAME=Administrator
ORACULO_MODEL_PATH=app/ml/pipeline_produccion.pkl
ORACULO_JWT_SECRET_KEY=replace-this-with-a-long-random-secret-at-least-32-chars
ORACULO_JWT_ALGORITHM=HS256
ORACULO_ACCESS_TOKEN_EXPIRE_MINUTES=60
ORACULO_ALLOWED_HOSTS=localhost,127.0.0.1,*.hf.space,*.huggingface.co
ORACULO_CORS_ALLOW_ORIGINS=http://localhost:3000,http://127.0.0.1:3000
ORACULO_MAX_REQUEST_SIZE_BYTES=32768
ORACULO_RATE_LIMIT_ENABLED=true
ORACULO_RATE_LIMIT_REQUESTS=60
ORACULO_RATE_LIMIT_WINDOW_SECONDS=60
ORACULO_DOCS_ENABLED=true
```
---
## 🚀 Instalación local paso a paso
### 1) Clonar el proyecto
```bash
git clone https://github.com/DiiegoA/Proyecto_modelo_IA.git
cd Proyecto_modelo_IA/oraculo_api
```
### 2) Crear entorno virtual
#### Linux / macOS
```bash
python -m venv .venv
source .venv/bin/activate
```
#### Windows (PowerShell)
```powershell
python -m venv .venv
.\.venv\Scripts\Activate.ps1
```
### 3) Instalar dependencias
```bash
pip install --upgrade pip
pip install -r requirements.txt
```
### 4) Configurar variables de entorno
```bash
cp .env.example .env
```
En Windows, copia el archivo manualmente o usa el explorador.
### 5) Ejecutar migraciones
```bash
alembic upgrade head
```
### 6) Iniciar el servidor
```bash
uvicorn app.main:app --reload
```
### 7) Abrir documentación interactiva
- Swagger UI: `http://127.0.0.1:8000/docs`
- ReDoc: `http://127.0.0.1:8000/redoc`
---
## 🧪 Cómo probar la API
### Health checks
```bash
curl http://127.0.0.1:8000/
curl http://127.0.0.1:8000/api/v1/health/live
curl http://127.0.0.1:8000/api/v1/health/ready
```
### Registrar usuario
```bash
curl -X POST http://127.0.0.1:8000/api/v1/auth/register \
-H "Content-Type: application/json" \
-d '{
"email": "user@example.com",
"full_name": "Test User",
"password": "StrongPass!123"
}'
```
### Login
```bash
curl -X POST http://127.0.0.1:8000/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{
"email": "user@example.com",
"password": "StrongPass!123"
}'
```
### Crear predicción
```bash
curl -X POST http://127.0.0.1:8000/api/v1/predictions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer TU_TOKEN" \
-d '{
"age": 45,
"workclass": "Private",
"fnlwgt": 250000,
"education": "Masters",
"education.num": 14,
"marital.status": "Married-civ-spouse",
"occupation": "Exec-managerial",
"relationship": "Husband",
"race": "White",
"sex": "Male",
"capital.gain": 15000,
"capital.loss": 0,
"hours.per.week": 50,
"native.country": "United-States"
}'
```
### Listar historial
```bash
curl -H "Authorization: Bearer TU_TOKEN" \
"http://127.0.0.1:8000/api/v1/predictions?skip=0&limit=20"
```
### Filtrar historial
```bash
curl -H "Authorization: Bearer TU_TOKEN" \
"http://127.0.0.1:8000/api/v1/predictions?label=%3E50K&min_probability=0.8"
```
### Consultar una predicción por id
```bash
curl -H "Authorization: Bearer TU_TOKEN" \
http://127.0.0.1:8000/api/v1/predictions/PREDICTION_ID
```
---
## 📡 Endpoints disponibles
### Salud
| Método | Ruta | Descripción |
| ------ | ---------------------- | ---------------------------------------- |
| `GET` | `/` | Metadatos básicos del servicio. |
| `GET` | `/api/v1/health/live` | Verifica que la API está viva. |
| `GET` | `/api/v1/health/ready` | Verifica base de datos + modelo cargado. |
### Autenticación
| Método | Ruta | Descripción |
| ------ | ----------------------- | --------------------------- |
| `POST` | `/api/v1/auth/register` | Registro de usuario. |
| `POST` | `/api/v1/auth/login` | Login y emisión de token. |
| `GET` | `/api/v1/auth/me` | Usuario autenticado actual. |
### Predicciones
| Método | Ruta | Descripción |
| ------ | ------------------------------------- | -------------------------------------------- |
| `POST` | `/api/v1/predictions` | Ejecuta una predicción y la persiste. |
| `GET` | `/api/v1/predictions` | Lista historial paginado del usuario. |
| `GET` | `/api/v1/predictions/{prediction_id}` | Recupera una predicción puntual del usuario. |
---
## 🧾 Contrato de entrada para predicción
El payload que espera la API está alineado con el dataset Adult Income y admite aliases con puntos:
| Campo HTTP | Campo normalizado | Tipo |
| ---------------- | ----------------- | ---------------- |
| `age` | `age` | `int` |
| `workclass` | `workclass` | `str` |
| `fnlwgt` | `fnlwgt` | `int` |
| `education` | `education` | `str` |
| `education.num` | `education_num` | `int` |
| `marital.status` | `marital_status` | `str` |
| `occupation` | `occupation` | `str` |
| `relationship` | `relationship` | `str` |
| `race` | `race` | `str` |
| `sex` | `sex` | `Male \| Female` |
| `capital.gain` | `capital_gain` | `int` |
| `capital.loss` | `capital_loss` | `int` |
| `hours.per.week` | `hours_per_week` | `int` |
| `native.country` | `native_country` | `str` |
### Reglas de validación destacadas
- `age`: entre `17` y `100`
- `fnlwgt`: entre `1` y `2_000_000`
- `education.num`: entre `1` y `16`
- `capital.gain`: entre `0` y `100_000`
- `capital.loss`: entre `0` y `10_000`
- `hours.per.week`: entre `1` y `99`
- categorías no vacías, sin caracteres de control y con longitud máxima razonable
---
## 📚 Respuesta de predicción
Una predicción devuelve información útil tanto para negocio como para auditoría:
```json
{
"id": "uuid",
"prediction": ">50K",
"probability": 0.91,
"is_counterfactual_applied": false,
"execution_time_ms": 12.34,
"model_version": "1.0.0",
"request_id": "uuid",
"created_at": "2026-01-01T00:00:00Z",
"input_payload": {},
"normalized_payload": {}
}
```
### Diferencia entre `input_payload` y `normalized_payload`
- `input_payload`: conserva la forma del contrato HTTP, incluyendo aliases como `education.num`.
- `normalized_payload`: usa nombres internos Python (`education_num`, `hours_per_week`, etc.).
Esto es útil para depuración, auditoría y trazabilidad del contrato público frente al contrato interno.
---
## 🧪 Tests
La suite existente valida el comportamiento público del backend con una estrategia pragmática:
### Qué se prueba
- registro exitoso;
- rechazo por usuario duplicado;
- login correcto;
- login con credenciales inválidas;
- endpoint `/me` protegido;
- raíz `/`;
- `health/live` y `health/ready`;
- creación de predicción autenticada;
- rechazo de payload inválido;
- rechazo de payload excesivo;
- filtros por `label` y `min_probability`;
- consulta por `prediction_id`;
- aislamiento de historial entre usuarios;
- mapeo controlado de errores del modelo.
### Cómo se ejecutan
```bash
pytest -q
```
### Estrategia de pruebas
La suite usa un `FakeModelManager` en `tests/conftest.py` para:
- desacoplar el contrato HTTP del artefacto real;
- acelerar las pruebas;
- validar reglas de negocio y seguridad sin depender siempre del `.pkl`.
---
## 🛠️ Migraciones con Alembic
### Aplicar migraciones
```bash
alembic upgrade head
```
### Crear una nueva migración
```bash
alembic revision --autogenerate -m "descripcion"
```
### Revertir una migración
```bash
alembic downgrade -1
```
### Qué hace `alembic/env.py`
- carga `Settings` reales del entorno;
- inyecta `settings.database_url` en la configuración de Alembic;
- usa `Base.metadata` como fuente de verdad del esquema;
- permite migraciones offline y online.
---
## 👤 Seed automático de administrador
Si activas estas variables:
```env
ORACULO_AUTO_SEED_ADMIN=true
ORACULO_SEED_ADMIN_EMAIL=admin@example.com
ORACULO_SEED_ADMIN_PASSWORD=ChangeMe!12345
ORACULO_SEED_ADMIN_NAME=Administrator
```
al arrancar la aplicación se crea un administrador si no existe uno previo con ese email.
Esto es útil para bootstrap local, demos o primeros despliegues.
---
## 🐳 Docker
### Qué hace el `Dockerfile`
- usa `python:3.11-slim` como base;
- instala `libgomp1` para dependencias del stack de ML;
- crea un usuario no root;
- instala dependencias desde `requirements.txt`;
- copia el proyecto completo al contenedor;
- expone el puerto `7860`;
- ejecuta `alembic upgrade head` antes de levantar Uvicorn.
### Construcción
```bash
docker build -t oraculo-api .
```
### Ejecución
```bash
docker run --rm -p 7860:7860 --env-file .env oraculo-api
```
### Comando final del contenedor
```bash
alembic upgrade head && uvicorn app.main:app --host 0.0.0.0 --port 7860
```
---
## 🤗 Despliegue en Hugging Face Spaces
Este repositorio ya está preparado para un **Docker Space**.
### Puntos importantes
- El front matter al inicio del README define `sdk: docker`, `app_port: 7860` y `base_path: /docs`.
- El contenedor escucha en `7860`.
- Swagger queda accesible en `/docs`.
- Los headers de seguridad contemplan el caso especial de embebido en dominios `*.hf.space` y `*.huggingface.co`.
### Variables mínimas recomendadas en Spaces
- `ORACULO_JWT_SECRET_KEY`
- `ORACULO_ALLOWED_HOSTS`
- `ORACULO_DOCS_ENABLED`
- `ORACULO_DATABASE_URL`
- `ORACULO_SEED_ADMIN_EMAIL`
- `ORACULO_SEED_ADMIN_PASSWORD`
### Persistencia en Spaces
Si quieres que SQLite sobreviva reinicios, usa almacenamiento persistente y configura:
```env
ORACULO_DATABASE_URL=sqlite:////data/oraculo.db
```
---
## ☁️ Despliegue en Render
### Start Command sugerido
```bash
alembic upgrade head && uvicorn app.main:app --host 0.0.0.0 --port $PORT
```
### Variables mínimas recomendadas
```env
ORACULO_ENVIRONMENT=production
ORACULO_DATABASE_URL=<postgres-url>
ORACULO_JWT_SECRET_KEY=<secret-largo>
ORACULO_ALLOWED_HOSTS=<tu-dominio>
ORACULO_DOCS_ENABLED=false
```
---
## 🧯 Troubleshooting
### 1. El modelo no carga
Revisa:
- que `ORACULO_MODEL_PATH` apunte realmente a `app/ml/pipeline_produccion.pkl`;
- que el archivo exista dentro del contenedor o del entorno local;
- que `joblib` pueda deserializar el artefacto;
- que, si usas un manifiesto, `model_manifest.json` esté bien formado.
### 2. `ready` devuelve degradado o error
Revisa:
- conexión a la base de datos;
- permisos del archivo SQLite;
- existencia del `.pkl`;
- carga correcta del modelo al arrancar.
### 3. El `App` tab de Hugging Face no muestra la app aunque `/docs` responde
Esto suele estar relacionado con headers de embebido. El proyecto ya contempla el caso `*.hf.space`, pero vale la pena revisar:
- `ORACULO_ALLOWED_HOSTS`;
- si el Space tiene la última versión desplegada;
- si hiciste `Factory reboot` tras cambios de seguridad;
- si `ORACULO_DOCS_ENABLED=true`.
### 4. SQLite se reinicia
Si el proveedor no tiene almacenamiento persistente, la BD local es efímera. En producción real, usa Postgres o un volumen persistente.
---
## 📈 Mejoras futuras naturales
Si quieres endurecer todavía más esta API, las siguientes mejoras son coherentes con la arquitectura actual:
- rate limiting distribuido con Redis;
- refresh tokens;
- observabilidad con Prometheus u OpenTelemetry;
- roles más finos (`admin`, `analyst`, `service`);
- pipeline CI con lint, type-check y cobertura;
- separación formal entre API pública e interna;
- Postgres como default de desarrollo colaborativo;
- versionado explícito del contrato de inferencia y del manifiesto del modelo.
---
## ✅ Resumen ejecutivo
`oraculo_api` ya no es un backend improvisado alrededor de un notebook.
Hoy es una base con:
- arquitectura por capas;
- contrato HTTP claro;
- autenticación JWT;
- hashing robusto de contraseñas;
- middlewares de seguridad;
- validación estricta de payloads;
- persistencia auditada de predicciones;
- health checks reales;
- migraciones con Alembic;
- compatibilidad con artefactos exportados desde notebook;
- pruebas automatizadas sobre el contrato principal.
En otras palabras: esta carpeta es la **capa de inferencia seria y trazable** del ecosistema Oráculo.
---
## 📌 Recomendación final de uso
Si alguien entra por primera vez a `oraculo_api`, el orden ideal para entenderlo es:
1. `README.md`
2. `app/main.py`
3. `app/api/v1/endpoints/`
4. `app/services/`
5. `app/db/`
6. `app/ml/model_manager.py`
7. `app/ml/custom_transformers.py`
8. `tests/`
Ese recorrido permite entender primero la interfaz pública, luego la lógica de negocio, después la persistencia y, por último, la capa de compatibilidad del modelo.