Serie MLOps y Producción · de la ingeniería de datos al monitoreo.
Módulo V — Monitoreo, interpretabilidad y comunicación
El curso entrenó, orquestó, publicó y vigiló. Nada de eso decide un presupuesto si el informe dice “F1 = 0.63” y el lector no sabe si debe esperar, reentrenar o apagar.
Este artículo cierra la serie: atar un número a una acción y a un riesgo. No levantamos Streamlit. El tablero es Matplotlib —las figuras que un Streamlit solo coloca—. El reporte es Markdown generado por código.
Objetivos
Elegir métricas según la decisión, no según el print de sklearn.
Armar un tablero mínimo (tendencia + semáforo).
Escribir el riesgo en tres audiencias.
Generar un reporte automático a partir de números, no de memoria.
Qué no cubre
Reentrenar (sesión 6) ni PSI (sesión 12): aquí se consumen.
Ruta
Una métrica no es una decisión.
Serie ya observada.
Semáforo y tablero.
Tres audiencias.
Reporte automático.
Taller de cierre.
from pathlib import Pathfrom datetime import dateimport numpy as npimport pandas as pdimport matplotlib.pyplot as pltRUTA = Path("artefactos_sesion14")RUTA.mkdir(exist_ok=True)np.random.seed(42)print("Entorno listo. Esta sesión no entrena: redacta.")
Entorno listo. Esta sesión no entrena: redacta.
1) Una métrica no es una decisión
En una frase: comunicar técnicamente es atar un número a una acción posible y a un riesgo si esa acción falla.
“F1 = 0.63” no dice qué hacer. “F1 de la semana 7 cayó bajo el umbral 0.55: no promover; abrir reentrenamiento” sí.
Estos números son el input de la comunicación, no un resultado nuevo de sklearn. Inventarlos a mano en un informe real sería fraude; aquí simulan la salida de la sesión 12.
3) Semáforo: tres estados, tres acciones
def semaforo(fila):if (not fila["cerrada"]):return"ámbar", "semana abierta: solo vigilar PSI"if fila["f1"] < UMBRAL_F1 and fila["psi"] > UMBRAL_PSI:return"rojo", "reentrenar y no promover"if fila["f1"] < UMBRAL_F1:return"rojo", "rollback y diagnóstico (posible concept drift)"if fila["psi"] > UMBRAL_PSI:return"ámbar", "vigilar: foto de x cambió, F1 aún sostiene"return"verde", "mantener production"estados = semanas.apply(semaforo, axis=1, result_type="expand")semanas["estado"] = estados[0]semanas["accion"] = estados[1]print(semanas[["semana", "f1", "psi", "estado", "accion"]].to_string(index=False))
semana f1 psi estado accion
1 0.62 0.04 verde mantener production
2 0.61 0.05 verde mantener production
3 0.63 0.06 verde mantener production
4 0.60 0.08 verde mantener production
5 0.59 0.11 verde mantener production
6 0.57 0.18 verde mantener production
7 0.48 0.31 rojo reentrenar y no promover
8 0.46 0.34 rojo reentrenar y no promover
El semáforo es un contrato de lenguaje. Verde no significa “el modelo es justo” (sesión 13). Significa “no se dispara la acción de esta política”.
Estado: rojo
Acción: reentrenar y no promover
F1=0.46 (umbral 0.55) | PSI=0.34 (umbral 0.25)
6) Tres audiencias, tres textos
El mismo hecho (rojo, semana 8) no se narra igual al comité, al ingeniero on-call y al regulador. Mentir por omisión en una audiencia es tan grave como inventar el F1.
def textos(fila):return {"comite": (f"Recomendación: no promover y abrir reentrenamiento. "f"El F1 semanal ({fila['f1']:.2f}) quedó bajo el mínimo acordado ({UMBRAL_F1}) "f"y la foto de los datos (PSI={fila['psi']:.2f}) también superó su umbral." ),"oncall": (f"SEMAFORO=rojo semana={int(fila['semana'])} f1={fila['f1']:.2f} psi={fila['psi']:.2f}. "f"Acción: rollback al run_id previo (sesión 8/11) y disparar job de train." ),"regulador": (f"El sistema permanece en production bajo política de umbrales prepublicados. "f"Se registra desviación de desempeño y de estabilidad poblacional. "f"La explicación caso a caso (sesión 13) no se sustituye por este agregado." ), }for k, v in textos(ultima).items():print("===", k, "===")print(v)print()
=== comite ===
Recomendación: no promover y abrir reentrenamiento. El F1 semanal (0.46) quedó bajo el mínimo acordado (0.55) y la foto de los datos (PSI=0.34) también superó su umbral.
=== oncall ===
SEMAFORO=rojo semana=8 f1=0.46 psi=0.34. Acción: rollback al run_id previo (sesión 8/11) y disparar job de train.
=== regulador ===
El sistema permanece en production bajo política de umbrales prepublicados. Se registra desviación de desempeño y de estabilidad poblacional. La explicación caso a caso (sesión 13) no se sustituye por este agregado.
7) Riesgos que hay que decir en voz alta
Si hacemos
Riesgo
Si no hacemos
Reentrenar ya
Hornear un drift de captura
Seguir decidiendo mal
Rollback
Volver a un modelo viejo
Dejar el degradado
Esperar otra semana
Más casos mal etiquetados
Reaccionar a un falso positivo de PSI
La comunicación técnica enumera ambos lados. Un informe que solo lista beneficios es publicidad.
8) Reporte automático: el Markdown no se escribe de memoria
reporte =f'''# Reporte semanal de modelo — semana {int(ultima["semana"])}**Fecha:** {date.today().isoformat()}**Estado:** {ultima["estado"]}**Acción recomendada:** {ultima["accion"]}## Métricas de decisión- F1: {ultima["f1"]:.2f} (umbral {UMBRAL_F1})- PSI: {ultima["psi"]:.2f} (umbral {UMBRAL_PSI})- Recall (contexto): {ultima["recall"]:.2f}## Lectura para comité{textos(ultima)["comite"]}## Lectura para operación{textos(ultima)["oncall"]}## Nota de gobernanza{textos(ultima)["regulador"]}## Qué no dice este reporte- No certifica equidad (sesión 13).- No sustituye el hash de datos (sesión 8).- No autoriza a bajar el umbral para poner verde (sesión 11).'''(RUTA /"reporte.md").write_text(reporte, encoding="utf-8")print(reporte)
# Reporte semanal de modelo — semana 8
**Fecha:** 2026-09-26
**Estado:** rojo
**Acción recomendada:** reentrenar y no promover
## Métricas de decisión
- F1: 0.46 (umbral 0.55)
- PSI: 0.34 (umbral 0.25)
- Recall (contexto): 0.38
## Lectura para comité
Recomendación: no promover y abrir reentrenamiento. El F1 semanal (0.46) quedó bajo el mínimo acordado (0.55) y la foto de los datos (PSI=0.34) también superó su umbral.
## Lectura para operación
SEMAFORO=rojo semana=8 f1=0.46 psi=0.34. Acción: rollback al run_id previo (sesión 8/11) y disparar job de train.
## Nota de gobernanza
El sistema permanece en production bajo política de umbrales prepublicados. Se registra desviación de desempeño y de estabilidad poblacional. La explicación caso a caso (sesión 13) no se sustituye por este agregado.
## Qué no dice este reporte
- No certifica equidad (sesión 13).
- No sustituye el hash de datos (sesión 8).
- No autoriza a bajar el umbral para poner verde (sesión 11).
Si el número cambia y el Markdown no se regenera, el informe es un souvenir. El hábito: el reporte lo emite el job de la sesión 7, no un copy-paste del viernes.
9) Métrica de contexto: recall no desempata el semáforo
Recall está en el reporte porque el daño de un falso negativo puede importar. No está en la función semaforo de este laboratorio. Si negocio quiere que mande, se cambia la política en un commit, no en una reunión verbal.
print("¿El recall de la semana 8 cambiaría el rojo?:", ultima["recall"] <0.45)print("Hoy: no. La política publicada usa F1 y PSI. Punto.")
¿El recall de la semana 8 cambiaría el rojo?: True
Hoy: no. La política publicada usa F1 y PSI. Punto.
10) Cierre de la serie: mapa de una frase
Sesión
Frase que este reporte hereda
4–5
\(T\) y \(f\) viajan juntos
6
el F1 no es un torneo sobre test
7
el reporte es un nodo, no un rito
8
cada cifra cita un hash
9–10
se sirve un contrato empaquetado
11
el umbral no se rebaja para el verde
12
PSI y F1 son radares distintos
13
el agregado no explica esta persona
14b) Anti-reporte: lo que no se entrega
anti = ["un screenshot de 14 métricas sin umbral","accuracy como titular","el F1 del diálogo 0.91 (sesión 6)","un GIF de Streamlit sin acción",]print("\n".join(f"- {a}"for a in anti))
- un screenshot de 14 métricas sin umbral
- accuracy como titular
- el F1 del diálogo 0.91 (sesión 6)
- un GIF de Streamlit sin acción
14c) El semáforo de la semana 6 (ámbar de PSI)
s6 = semanas.iloc[5]print(s6[["semana", "f1", "psi", "estado", "accion"]].to_string())print("Útil para enseñar: ámbar no es 'todo bien' ni 'apaga'.")
semana 6
f1 0.57
psi 0.18
estado verde
accion mantener production
Útil para enseñar: ámbar no es 'todo bien' ni 'apaga'.
14d) Una frase de examen
“El entregable no es el F1; es una acción con umbral publicado, un riesgo si se toma y un riesgo si no se toma, en el idioma de quien decide.”
14e) One-pager de 90 segundos
Un comité no lee ocho semanas. El one-pager: estado, una cifra, una acción, un riesgo. El anexo lleva la tabla. Quien invierte el orden (anexo primero) pierde la sala.
print(f"ONE-PAGER: {ultima['estado'].upper()} | F1 {ultima['f1']:.2f} | {ultima['accion']}")
ONE-PAGER: ROJO | F1 0.46 | reentrenar y no promover
14f) RACI mínimo
Acción
Responsible
Accountable
Consulted
Rollback
on-call
dueño del servicio
ciencia de datos
Bajar umbral
—
producto + riesgo
no el on-call solo
Reentrenar
MLOps
ciencia de datos
negocio
14g) Glosario para no especialistas
glosario = {"F1": "equilibrio entre acertar los positivos y no inventarlos","PSI": "qué tanto cambió la foto de los datos de entrada","umbral": "número acordado *antes* para no discutir con el gráfico","rojo": "hay que actuar, no 'el modelo es malo en abstracto'",}for k, v in glosario.items():print(f"{k}: {v}")
F1: equilibrio entre acertar los positivos y no inventarlos
PSI: qué tanto cambió la foto de los datos de entrada
umbral: número acordado *antes* para no discutir con el gráfico
rojo: hay que actuar, no 'el modelo es malo en abstracto'
14h) Agenda de la reunión de semáforo
Estado (30 s). 2. Cifra y umbral (1 min). 3. Acción propuesta (1 min). 4. Riesgos (2 min). 5. Dueño y fecha (30 s). Quien empiece por la arquitectura del bosque ha perdido.
14i) Versión del reporte
print("reporte.md versionado con: fecha, umbrales, semana, estado")print("un PDF sin esos cuatro campos es una foto, no un registro")
reporte.md versionado con: fecha, umbrales, semana, estado
un PDF sin esos cuatro campos es una foto, no un registro
14j) Traducir recall a costo
Si cada falso negativo cuesta \(c\) y cada falso positivo \(k\), el titular puede ser costo esperado, no F1. Entonces el semáforo cambia de métrica —en el repo, no en la pizarra.
c_fn, c_fp =10, 1print(f"si negocio firma c_fn={c_fn} x c_fp={c_fp}, el reporte titularía costo, no F1")
si negocio firma c_fn=10 x c_fp=1, el reporte titularía costo, no F1
14k) No usar jerga de las sesiones 4–5 en el comité
ColumnTransformer no se menciona. Se dice “el dato crudo entra igual que el día del entrenamiento”. La jerga queda en el anexo técnico.
14l) Pregunta que cierra la reunión
“¿Quién hace qué antes de qué fecha?” Si no hay respuesta, hubo teatro de métricas. El reporte automático deja huecos para dueño y fecha; el humano los llena.
print("dueño: ________ fecha: ________ (no lo inventa el Markdown)")
dueño: ________ fecha: ________ (no lo inventa el Markdown)
14m) Serie completa en una oración
Diseñamos \(T\), la encapsulamos, elegimos \(f\) sin pescar el test, orquestamos, nombramos la corrida, la servimos en una caja portable, la máquina pulsó el botón, vigilamos el mundo que se movió, abrimos un caso, y ahora alguien firma una acción.
14n) Lo que este cierre no promete
No promete que el F1 suba. Promete que la próxima decisión no se tomará de memoria. Esa es la diferencia entre un curso de modelos y un curso de producción.
15) Errores frecuentes
Titular con accuracy.
Ocultar el umbral.
Un solo texto para tres audiencias.
Regenerar el reporte a mano.
Bajar el umbral en el acta, no en el repo.
16) Checklist
¿La métrica de decisión está declarada?
¿El umbral está pintado?
¿Hay acción, no solo color?
¿Hay riesgo de actuar y de no actuar?
¿El Markdown lo emite el código?
17) Conclusiones
Comunicar un sistema inteligente es el último contrato de la serie: el que une números a una firma. El tablero, el semáforo y el reporte automático no sustituyen las sesiones 6 a 13; las hacen decidibles. Si el lector no sabe qué hacer al terminar el informe, no hubo comunicación técnica: hubo un anexo de métricas.
18) Taller de cierre
A. Cambie UMBRAL_F1 a 0.47. ¿Sigue rojo la semana 8? ¿Debe cambiarse el umbral así?
B. Redacte el párrafo de comité para la semana 6 (ámbar).
C. Añada al reporte el run_id inventado run_prod_08.
D. Escriba el riesgo de esperar otra semana en una línea.
E. En 8 líneas, cuente la serie del curso a alguien que no la cursó, sin citar librerías.
print("Reporte:", (RUTA /"reporte.md").exists(), "| tablero:", (RUTA /"tablero.png").exists())print("Estado final:", ultima["estado"], "→", ultima["accion"])print("Taller A–E. Si el informe no recomienda, no cierra el curso.")
Reporte: True | tablero: True
Estado final: rojo → reentrenar y no promover
Taller A–E. Si el informe no recomienda, no cierra el curso.
💬 ¿Te sirvió?
Deja en los comentarios una duda o un caso donde aplicarías esto — respondo todos. Sígueme para no perderte el próximo artículo de la serie y comparte con alguien que esté aprendiendo arquitectura de IA.
👉 El código completo está disponible para ejecutar directamente.