5 · Cargar el modelo, validar la entrada, manejar errores
Una ruta de grado producción carga el modelo `.joblib` UNA VEZ al arrancar (no por petición), valida el payload antes de predecir (claves presentes, numéricas, en rango), y envuelve el trabajo en `try/except` para devolver errores estructurados con los códigos de estado correctos — sin confiar nunca en la entrada del cliente.
Una ruta robusta hace tres cosas que la ingenua omite: carga el modelo `.joblib` UNA VEZ a nivel de módulo (no por petición, que es lento), VALIDA el payload ANTES de predecir (claves presentes, numéricas, finitas, en rango), y envuelve el trabajo en `try/except` para devolver errores ESTRUCTURADOS con códigos de estado correctos (`422` entrada mala, `500` fallo de servidor) — porque nunca confías en la entrada del cliente.
Sin esto:
Cargar el modelo por petición hace cada llamada lenta; omitir la validación deja que la entrada malformada tumbe al worker o — peor — devuelva una predicción segura y sin sentido; y las excepciones no manejadas filtran trazas de pila y devuelven códigos de estado incorrectos.
Las lecciones 3 y 4 hicieron funcionar la ruta. Esta lección la hace robusta — la diferencia entre un demo y algo que pondrías frente a tráfico real. Tres disciplinas:
1. Carga el modelo UNA VEZ, al arrancar. Pon model = joblib.load(...) a nivel de módulo — corre una sola vez cuando la app inicia. No lo cargues dentro de la ruta, o releerás y deserializarás el archivo en cada petición, haciendo cada predicción innecesariamente lenta y desperdiciando memoria. Cargar es lento; las peticiones deben ser rápidas; así que pagas el costo de carga una vez y reusas el modelo en memoria para siempre después. (De regalo, si el archivo del modelo falta, la app falla en voz alta al arrancar en vez de en la petición del primer usuario.)
2. Valida el payload ANTES de predecir. Este es el corazón de la lección. model.predict tiene un contrato estricto — un conjunto fijo de características, todas numéricas, finitas, dentro de un rango sensato — y no sabe nada de imponerlo. Así que tú lo impones, con una compuerta de validación que corre primero y verifica, en orden:
- Claves presentes — cada característica requerida está en el payload (si no,
422, nombra la que falta). - Numéricas — cada valor se convierte a
float(si no,422, un string como"high"se rechaza). - Finitas — sin
NaN/inf(estos pasan los chequeos de tipo pero envenenan el modelo). - En rango — dentro de los límites que esperas (un
tenurede 999 años es casi seguro un bug).
Solo si todos los chequeos pasan llamas a model.predict. Una petición mala se vuelve un 422 claro con un mensaje que dice exactamente qué está mal — no un crash críptico, y nunca una respuesta silenciosamente incorrecta.
3. Maneja errores con try/except y el código de estado correcto. Envuelve la predicción en try/except. Un fallo de validación es culpa del cliente → devuelve 422 (o 400) con un error JSON estructurado como {"error": "..."}. Un fallo inesperado es culpa del servidor → devuelve 500, registra la traza real en el servidor, pero envía al cliente un mensaje genérico (nunca filtres una traza de pila — es ruido para ellos y un riesgo de seguridad para ti). Cada respuesta es intencional: un resultado, o un error estructurado con el código correcto.
¿Por qué validar en el SERVIDOR aun cuando el formulario ya valida en el cliente? Porque la validación del lado del cliente es una conveniencia, no una garantía. Los atributos HTML required/type="number" dan al usuario retroalimentación instantánea, pero se evaden trivialmente — cualquiera puede desactivar JavaScript, editar el HTML, o saltarse el formulario por completo y hacer curl a tu endpoint con los bytes que quiera. El servidor es el único lugar que realmente controlas, así que es el único lugar donde la validación es imponible. La validación del cliente mejora la UX; la validación del servidor es la verdadera frontera de seguridad.
La celda de solo-lectura muestra la ruta endurecida. La celda ejecutable de abajo es la compuerta de validación misma — rechazando payloads con clave faltante, no numéricos, NaN, y fuera de rango con errores claros, aceptando uno válido, luego prediciendo — sobre un modelo pequeño entrenado.
Una ruta `/predict` de Flask endurecida: modelo cargado una vez a nivel de módulo, una compuerta de validación (claves presentes, numéricas, finitas, en rango) antes de predecir, y `try/except` devolviendo `422` para entrada mala y `500` (con un log en el servidor) para fallos inesperados.
Python (in browser)
Python runs entirely in your browser via Pyodide (~6 MB on first Run, cached after).
Tu formulario HTML ya marca cada campo como `required` y `type="number"`. ¿Por qué la ruta de Flask DEBE TAMBIÉN validar la entrada en el servidor?
- Carga el modelo `.joblib` UNA VEZ a nivel de módulo para que arranque una sola vez y se reuse — nunca dentro de la ruta, lo que lo recargaría en cada petición.
- Valida el payload ANTES de predecir — claves presentes, numéricas, finitas, en rango — y devuelve un `422` claro nombrando exactamente qué está mal en caso de fallo.
- Nunca confíes en la entrada del cliente: la validación del cliente es solo UX y se evade fácilmente, así que la compuerta del servidor es la verdadera frontera de seguridad — devuelve `422` para entrada mala, `500` (con un log en el servidor) para fallos genuinos.
Todo servicio de modelo en producción carga su artefacto una vez al arrancar y filtra cada petición por validación de entrada — es la forma estándar que mantiene a un predictor en tiempo real rápido y confiable.
Si lo quitas: La carga por petición hace lento al servicio, y una ruta sin validar o se cae ante entrada malformada o devuelve basura segura — ambos inaceptables cuando una predicción guía una decisión real.