lección 8
Bonus - Alertas de calidad de datos
Marcos te asigna una tarea crítica: montar alertas para que nunca más se pierdan 3 días de eventos sin que nadie se entere. Volumen, frescura, distribución y unicidad.
⏱ 40 min
### La semana siguiente: el problema que nadie vio
Tu segunda semana en TaskFlow empieza con una anécdota de Marcos en el standup del lunes. El tono es diferente al de la semana pasada -- hay frustración contenida. Es la primera vez que ves a Marcos ligeramente molesto:
1#data-team | Lunes 9:45 AM (semana 2)23Marcos Delgado 9:47 AM4Contexto para el nuevo: el mes pasado, antes de que llegaras,5el evento board_viewed dejo de emitirse durante 3 días.6Un deploy de backend rompio el tracking de boards sin querer.7Nadie se entero hasta que Sandra noto que un dashboard de8Looker mostraba ceros. Para entonces ya habiamos perdido93 días de datos irrecuperables.1011Eso no puede volver a pasar. Tu tarea está semana:12montar alertas de calidad de datos para el pipeline de13eventos. Si algo falla, nos enteramos en horas, no en días.1415Sandra Vidal 9:50 AM16+1 ?? Las métricas que construimos la semana pasada dependen17de que los eventos lleguen. Si dejan de llegar y no nos18enteramos, los números de retención son mentira.19Y no puedes recalcular cohortes con datos que no existen.2021Pablo Herrera 9:52 PM22?? Prioridad alta. Ticket DATA-048. Marcos te hace de23sparring para el diseño.2425Lucia Fernandez 9:54 AM26Ey, +1 ?? Si los datos de retención que mostró el nuevo27la semana pasada hubieran estado basados en 3 días menos28de eventos... uf. Me habria presentado al board con números29incorrectos.3031 ?? 3 ? 2
La reacción de Lucía es la que te golpea: si tu análisis de cohortes se hubiera construido sobre datos incompletos sin saberlo, Lucía habría presentado números erróneos al board. Y la credibilidad del equipo de datos (incluida la tuya) se habría destruido. Las alertas de calidad no son un nice-to-have -- son un ESCUDO que protege la reputación del equipo.
### 10:00 AM - Call con Marcos: disenar las alertas
Marcos te llama a las 10. Hoy está más hablador de lo normal -- el tema le importa personalmente. Fue él quien se enteró 3 días tarde del incidente del mes pasado y se siente responsable.
### Las 4 dimensiones de la calidad de eventos
Marcos te explica en una call rápida las 4 dimensiones que necesitas monitorizar para un pipeline de eventos:
- 01.VOLUMEN: cuántos eventos llegan por hora/día? Si de repente llegan 0 (o 10x más de lo normal), algo está mal.
- 02.FRESCURA: cuál es el timestamp del evento más reciente? Si el último evento tiene más de 2 horas de antigüedad, el pipeline está atascado.
- 03.DISTRIBUCION: la proporción entre tipos de evento es estable? Si board_viewed pasa de ser el 28% a el 0%, algo se rompió.
- 04.UNICIDAD: el ratio de duplicados está en rango normal? Si de repente sube al 5%, hay un problema con el producer de Kafka.
1# data_quality_alerts.py - Sistema de alertas para eventos TaskFlow2import pandas as pd3from datetime import datetime, timedelta4from typing import NamedTuple56class AlertResult(NamedTuple):7 check_name: str8 status: str # 'OK', 'WARNING', 'CRITICAL'9 message: str10 value: float11 threshold: float1213def check_volume(df: pd.DataFrame, window_hours: int = 1) -> AlertResult:14 """15 Verifica que el volumen de eventos por hora está dentro de lo esperado.16 Esperado: 80K-150K eventos/hora en horario laboral.17 WARNING si < 50K. CRITICAL si < 10K o = 0.18 """19 now = datetime.utcnow()20 recent = df[df['event_timestamp'] >= now - timedelta(hours=window_hours)]21 volume = len(recent)2223 if volume == 0:24 return AlertResult('volume', 'CRITICAL',25 f'0 eventos en las últimas {window_hours}h', 0, 10000)26 elif volume < 10000:27 return AlertResult('volume', 'CRITICAL',28 f'Solo {volume:,} eventos (esperado: 80K+)', volume, 10000)29 elif volume < 50000:30 return AlertResult('volume', 'WARNING',31 f'{volume:,} eventos (esperado: 80K+)', volume, 50000)32 return AlertResult('volume', 'OK', f'{volume:,} eventos en {window_hours}h', volume, 80000)3334def check_freshness(df: pd.DataFrame, max_lag_minutes: int = 30) -> AlertResult:35 """36 Verifica que el evento más reciente no tiene más de X minutos.37 WARNING si > 30 min. CRITICAL si > 120 min.38 """39 now = datetime.utcnow()40 latest = df['event_timestamp'].max()41 lag_minutes = (now - latest).total_seconds() / 604243 if lag_minutes > 120:44 return AlertResult('freshness', 'CRITICAL',45 f'Ultimo evento hace {lag_minutes:.0f} min', lag_minutes, 120)46 elif lag_minutes > max_lag_minutes:47 return AlertResult('freshness', 'WARNING',48 f'Ultimo evento hace {lag_minutes:.0f} min', lag_minutes, max_lag_minutes)49 return AlertResult('freshness', 'OK',50 f'Lag: {lag_minutes:.0f} min', lag_minutes, max_lag_minutes)5152def check_distribution(df: pd.DataFrame, window_hours: int = 24) -> AlertResult:53 """54 Verifica que la distribución de tipos de evento es estable.55 Si un tipo que normalmente es >5% cae a 0%, es CRITICAL.56 """57 now = datetime.utcnow()58 recent = df[df['event_timestamp'] >= now - timedelta(hours=window_hours)]5960 expected_types = ['board_viewed', 'task_created', 'task_completed',61 'comment_added', 'user_login']6263 missing = []64 for et in expected_types:65 if et not in recent['event_type'].values:66 missing.append(et)6768 if missing:69 return AlertResult('distribution', 'CRITICAL',70 f'Tipos ausentes: {missing}', len(missing), 0)71 return AlertResult('distribution', 'OK',72 'Todos los tipos presentes', 0, 0)7374def check_duplicates(df: pd.DataFrame, max_ratio: float = 0.02) -> AlertResult:75 """76 Verifica que el ratio de duplicados está en rango normal (<2%).77 WARNING si > 2%. CRITICAL si > 5%.78 """79 total = len(df)80 dupes = df['event_id'].duplicated().sum()81 ratio = dupes / total if total > 0 else 08283 if ratio > 0.05:84 return AlertResult('duplicates', 'CRITICAL',85 f'Ratio de duplicados: {ratio:.1%}', ratio, 0.05)86 elif ratio > max_ratio:87 return AlertResult('duplicates', 'WARNING',88 f'Ratio de duplicados: {ratio:.1%}', ratio, max_ratio)89 return AlertResult('duplicates', 'OK',90 f'Duplicados: {ratio:.1%}', ratio, max_ratio)9192def run_all_checks(df: pd.DataFrame) -> list:93 """Ejecuta todas las alertas y devuelve resultados."""94 results = [95 check_volume(df),96 check_freshness(df),97 check_distribution(df),98 check_duplicates(df),99 ]100101 print("=== DATA QUALITY REPORT ===")102 print(f"Timestamp: {datetime.utcnow().isoformat()}")103 print(f"Eventos analizados: {len(df):,}")104 print()105106 for r in results:107 icon = {'OK': '?', 'WARNING': '?', 'CRITICAL': '?'}[r.status]108 print(f" {icon} [{r.status:8}] {r.check_name}: {r.message}")109110 critical = [r for r in results if r.status == 'CRITICAL']111 if critical:112 print(f"\n?? {len(critical)} ALERTAS CRITICAS -- requieren accion inmediata")113114 return results
Sistema de alertas con 4 checks: volumen, frescura, distribución, unicidad
Consejo de senior: las alertas de calidad de datos son la diferencia entre "nos enteramos cuando el CEO pregunta por qué el dashboard está vacío" y "nos enteramos en 30 minutos y lo arreglamos antes de que nadie lo note". Montarlas es una inversión de 1 día que te ahorra semanas de dolor.
### Configurar el scheduling
Las alertas deben ejecutarse automáticamente cada hora. En TaskFlow usaríais un cron job o un DAG de Airflow. El output se publica en #data-quality de Slack. Si hay un CRITICAL, además se envía un DM a Marcos y a ti.
1# Configuracion del alerting (simplificado)2ALERT_CONFIG = {3 "schedule": "0 * * * *", # Cada hora en punto4 "slack_channel": "#data-quality",5 "critical_notify": ["marcos.delgado", "tu.nombre"],6 "thresholds": {7 "volume_warning": 50000,8 "volume_critical": 10000,9 "freshness_warning_min": 30,10 "freshness_critical_min": 120,11 "duplicate_warning": 0.02,12 "duplicate_critical": 0.05,13 },14 "expected_event_types": [15 "board_viewed", "task_created", "task_completed",16 "comment_added", "user_login", "task_assigned",17 ],18}1920# Ejemplo de mensaje Slack para un CRITICAL:21SLACK_CRITICAL_TEMPLATE = """22?? *DATA QUALITY ALERT - CRITICAL*23Check: {check_name}24Status: {status}25Message: {message}26Value: {value}27Threshold: {threshold}28Timestamp: {timestamp}29Action required: investigar inmediatamente30cc: @marcos.delgado31"""
Configuración de alertas: umbrales, canal de Slack y template
1Marcos Delgado (review del PR, viernes)23"Bien. Solo un cambio: anade un check de 'schema drift'.4Si de repente aparece un campo nuevo en properties que no5existía antes, quiero saberlo. No es crítico pero es útil6para detectar deploys de backend que cambian el tracking."78"Y anade un cooldown: si la alerta ya se disparo hace menos9de 1 hora, no la repitas. Si no, el canal se llena de spam10cuando algo está roto."1112"Aprobado con esos dos cambios. Buen trabajó."
Error común: montar alertas demasiado sensibles que se disparan constantemente (alert fatigue). Si el canal #data-quality tiene 50 alertas al día, nadie las lee. Los umbrales deben ser razonables: solo se disparan cuando hay un PROBLEMA REAL, no cuando hay una fluctuación normal.
## ejercicios
Implementar check de volumen con histórico
Mejora el check de volumen: en vez de un umbral fijo, compara con la media de los últimos 7 días a la misma hora. Si el volumen actual es <50% de la media histórica, es WARNING. Si es <20%, es CRITICAL.
Check de distribución con desviacion histórica
Implementa un check que compara la distribución actual de tipos de evento con la distribución histórica. Si un tipo cae más de 80% respecto a su porcentaje habitual, alerta.
Formatear alerta para Slack
Escribe una función que toma los resultados de todos los checks y genera un mensaje de Slack formateado con emojis, severidad y recomendaciones.
Regístrate para guardar tu progreso.
## comentarios
Reporta erratas, ayuda a otros o comparte tu opinión. Sé constructivo.
Inicia sesión para comentar y responder.
cargando comentarios...