lección 2

Día 1 - Explorar eventos y definir "usuario activo"

Abres los JSON de eventos por primera vez, descubres la estructura, negocias la definición de activo con Sandra y Lucia, y diseñas la tabla staging.

50 min

### Martes, 8:45 AM - Los eventos que nadie ha tocado

Segundo día. Te sientas con el café (cortado, dos de azúcar -- el de la máquina de TaskFlow es sorprendentemente bueno) y abres la terminal. Ayer Marcos te dio accesos al bucket de S3. Dentro de s3://taskflow-events/raw/ hay 47 carpetas -- una por tipo de evento. Los nombres ya te cuentan una historia: task_created, task_completed, task_assigned, board_viewed, comment_added, workspace_member_invited, subscription_upgraded... Seis meses de comportamiento de 50.000 usuarios, acumulándose en silencio.

1#data-team | Martes 8:50 AM
2
3Tu 8:50 AM
4Buenos días! Hoy planeo:
5- Explorar los eventos de S3 (estructura, volumenes, anomalias)
6- Hablar con Sandra sobre la definición de "usuario activo"
7- Empezar el diseño de la tabla staging
8
9Si necesito algo de alguien os aviso ??
10
11Pablo Herrera 8:52 AM
12?? Buen plan. Un consejo: cuando explores los eventos, busca
13cosas raras. Timestamps fuera de rango, campos vacios,
14duplicados. Es la primera vez que alguien mira esos datos
15con ojo crítico. Seguro que hay sorpresas.
16
17Sandra Vidal 8:54 AM
18Cuando llegues a la parte de definición de "activo" buscame ??
19Tengo opinion fuerte al respecto y quiero que estemos
20alineados ANTES de que construyas la tabla.
21
22 ?? 2

En FreshMart aprendiste la regla de oro: nunca transformes datos que no has explorado primero. Descargas un sample -- los eventos del día anterior para 3 tipos: task_created, board_viewed y comment_added. Unos 250.000 eventos en un archivo JSON Lines.

1{"event_id": "evt_7f3a2b1c", "event_type": "task_created", "timestamp": "2024-03-14T09:23:41.123Z", "user_id": "usr_4521", "workspace_id": "ws_0892", "properties": {"task_id": "tsk_18923", "board_id": "brd_0445", "has_assignee": true, "has_due_date": false, "priority": "medium"}}
2{"event_id": "evt_8d4c3e2f", "event_type": "task_created", "timestamp": "2024-03-14T09:23:41.456Z", "user_id": "usr_4521", "workspace_id": "ws_0892", "properties": {"task_id": "tsk_18924", "board_id": "brd_0445", "has_assignee": true, "has_due_date": true, "priority": "high"}}
3{"event_id": "evt_9e5d4f3a", "event_type": "board_viewed", "timestamp": "2024-03-14T09:24:02.789Z", "user_id": "usr_1107", "workspace_id": "ws_0334", "properties": {"board_id": "brd_0201", "view_duration_ms": 34521, "cards_visible": 23}}
4{"event_id": "evt_1a2b3c4d", "event_type": "comment_added", "timestamp": "2024-03-14T09:25:11.012Z", "user_id": "usr_2203", "workspace_id": "ws_0892", "properties": {"task_id": "tsk_18900", "comment_length": 142, "has_mention": true}}

Muestra de eventos: JSON Lines, un evento por línea, estructura consistente

Cada línea es un evento independiente. Estructura consistente: event_id único, tipo, timestamp ISO en UTC, user_id del usuario que lo disparó, workspace al que pertenece, y un objeto properties con detalles específicos del tipo de evento. El campo properties varía según el evento: task_created tiene priority y has_due_date; board_viewed tiene view_duration_ms; comment_added tiene comment_length y has_mention.

Esto es diferente a los CSVs de FreshMart donde cada archivo tenía columnas fijas. Aquí el MISMO formato contiene eventos con propiedades DIFERENTES. Es como si el CSV de ventas y el CSV de clientes estuvieran mezclados en un solo archivo, diferenciados solo por un campo "type". Eso tiene implicaciones para el diseño de la tabla: o haces una tabla por tipo de evento (47 tablas!) o haces una tabla unificada con un campo JSON para propiedades variables. Spoiler: vais a ir por la segunda opción.

### 9:00 AM - Exploracion con Pandas

Abres VS Code, creas un notebook de exploración y empiezas a investigar. Tu objetivo: entender volúmenes, tipos de eventos, distribución temporal y detectar anomalías. Es el mismo workflow que usabas con los CSVs de María, pero aquí los datos son JSON y son órdenes de magnitud más grandes.

Te acuerdas de lo que Elena te enseñó en FreshMart: "Antes de escribir una sola línea de transformación, responde estas 5 preguntas sobre los datos: cuántas filas hay, cuántas columnas, qué tipo de dato tiene cada una, cuántos NULLs hay, y hay duplicados?" Aquí aplica igual, solo que en vez de un CSV de María tienes 250K líneas de JSON.

1# explorar_eventos.py - Primera exploracion de eventos TaskFlow
2import pandas as pd
3import json
4from collections import Counter
5
6# Cargar JSON Lines (un JSON por linea)
7events = []
8with open('datos/eventos_producto.jsonl', 'r') as f:
9 for line in f:
10 events.append(json.loads(line))
11
12df = pd.DataFrame(events)
13
14print(f"Total eventos en muestra (3 días): {len(df):,}")
15print(f"Columnas: {list(df.columns)}")
16print(f"Tipos de dato:\n{df.dtypes}")
17print(f"\nTipos de evento únicos: {df['event_type'].nunique()}")
18print(f"\nTop 10 eventos por volumen:")
19print(df['event_type'].value_counts().head(10))
20print(f"\nUsuarios únicos: {df['user_id'].nunique():,}")
21print(f"Workspaces únicos: {df['workspace_id'].nunique():,}")
22
23# Rango temporal
24df['timestamp'] = pd.to_datetime(df['timestamp'])
25print(f"\nRango temporal:")
26print(f" Min: {df['timestamp'].min()}")
27print(f" Max: {df['timestamp'].max()}")
28print(f" Dias cubiertos: {(df['timestamp'].max() - df['timestamp'].min()).days}")
29
30# Buscar anomalias basicas
31print(f"\nNulls por columna:")
32print(df.isnull().sum())
33print(f"\nDuplicados por event_id: {df['event_id'].duplicated().sum():,}")

La primera exploración revela la escala: 750K eventos en 3 días

El resultado revela la escala: en 3 días hay ~750.000 eventos de 47 tipos distintos, generados por ~12.000 usuarios en ~3.400 workspaces. Los más frecuentes son board_viewed (28%), user_login (21%), task_created (15%), task_completed (12%) y comment_added (10%). Y la primera sorpresa: hay 1.847 event_ids duplicados. Los eventos duplicados existen. David te lo va a explicar el miércoles.

### 10:00 AM - La primera sorpresa: duplicados

1#data-eng | Martes 10:05 AM
2
3Tu 10:05 AM
4Pregunta para quien sepa: estoy explorando los eventos en S3
5y veo 1.847 event_ids duplicados en una muestra de 3 días.
6Es esperado o es un bug?
7
8Marcos Delgado 10:08 AM
9Esperado. Kafka at-least-once delivery. Preguntale a David.
10
11Sandra Vidal 10:09 AM
12Si, me he encontrado con eso antes ?? Cuando hagas el staging
13necesitas deduplicar por event_id. Usa ROW_NUMBER() particionado
14por event_id ordenado por timestamp, queda con el primero.
15
16Tu 10:11 AM
17Perfecto, gracias a los dos. Lo tendere en cuenta en el diseño
18de la tabla.
19
20 ?? 1

Consejo de senior: en sistemas event-driven, la deduplicación no es un edge case -- es un REQUISITO fundamental. Kafka, SQS, Kinesis... todos tienen escenarios donde un mensaje se entrega más de una vez. Si tu pipeline no deduplica, tus métricas están infladas. El patrón estándar es: INSERT con deduplicación por ID natural del evento + timestamp como desempate.

### 10:30 AM - La reunion con Sandra: que es un "usuario activo"

A las 10:30 Sandra te llama por Meet. Vio tu mensaje sobre los duplicados en #data-eng y quiere aprovechar para alinear algo crítico ANTES de que empieces a construir tablas. Comparte pantalla con su Miro abierto -- un diagrama con flechas y post-its.

1Sandra Vidal (Meet, 10:30 AM):
2
3"Oye, antes de que hagas nada con esos eventos necesitamos
4hablar de la definición de usuario activo. Ahora mismo hay
5TRES definiciones diferentes circulando por la empresa:
6
71. Lucia dice: usuario activo = alguien que ha hecho login.
8 Problema: login no implica uso real. Puedes hacer login,
9 mirar el dashboard 2 segundos y irte.
10
112. El CEO mira Amplitude: cuenta cualquier pageview.
12 Problema: un bot o un script de healthcheck cuenta como
13 usuario activo.
14
153. Mi propuesta: activo = alguien que ha hecho una ACCION
16 con INTENCION. Crear tarea, completar tarea, comentar,
17 asignar, subir archivo. No solo mirar.
18
19Si no decidimos ahora, vamos a tener 3 números de retención
20diferentes y eso es PEOR que no tener ninguno."
21
22Tu: "Tiene sentido. Y si definimos dos niveles? Core active
23para acciones con intencion, y broad active que incluye
24también board_viewed y search_performed? Asi reportamos el
25core para retención pero tenemos el broad para contexto."
26
27Sandra: "...me gusta mucho eso. Es lo que en Cabify
28llamabamos L1 activity y L2 activity. Vamos con eso.
29Haz un listado de que eventos entran en cada categoría
30y me lo pasas. Lo documento en el Notion de definiciones
31y se lo presentamos a Pablo juntos."
32
33Tu: "Hecho. Te lo tengo en una hora."
34
35Sandra: "Perfecto. Y oye... buena propuesta. Se nota que
36no es tu primer rodeo." (sonrie)

Ese último comentario de Sandra es importante. Recuerda que empezó con cierta desconfianza -- el trauma del junior anterior que rompio 4 modelos. Que te diga "se nota que no es tu primer rodeo" significa que tu approach de PREGUNTAR PRIMERO está funcionando. En FreshMart habrias ido directo a la query. Aqui preguntas, propones y alineas. Eso es crecer.

### 11:30 AM - Clasificar los 47 tipos de evento

Vuelves a tu exploracion. Necesitas clasificar cada tipo de evento en las categorías que acordaste con Sandra. Iteras sobre los event_types únicos y los agrupas:

1# clasificar_eventos.py - Mapeo de eventos a categorías
2# Definicion acordada con Sandra (y que presentaremos a Pablo)
3
4CORE_ACTIVE_EVENTS = [
5 'task_created', # Usuario crea una tarea
6 'task_completed', # Usuario marca tarea como completada
7 'task_assigned', # Usuario asigna tarea a alguien
8 'comment_added', # Usuario escribe un comentario
9 'file_uploaded', # Usuario sube un archivo
10 'board_created', # Usuario crea un tablero nuevo
11 'automation_created', # Usuario crea una automatización
12]
13
14BROAD_ACTIVE_EVENTS = CORE_ACTIVE_EVENTS + [
15 'board_viewed', # Ve un tablero (intencion ambigua)
16 'task_viewed', # Ve el detalle de una tarea
17 'search_performed', # Busca algo en el workspace
18 'filter_applied', # Aplica un filtro en un board
19 'notification_clicked', # Hace click en una notificación
20 'dashboard_viewed', # Ve un dashboard de proyecto
21 'report_exported', # Exporta un reporte
22]
23
24SYSTEM_EVENTS = [
25 'user_login', # Login (no implica uso)
26 'session_started', # Sesion técnica
27 'session_ended', # Fin de sesion
28 'email_opened', # Abrio un email transaccional
29 'push_notification_sent', # Se le envio un push
30 'password_reset_requested', # Pidio reset de password
31 'workspace_member_invited', # Invito a alguien (admin action)
32 'subscription_upgraded', # Cambio de plan (billing)
33 'subscription_downgraded', # Bajada de plan
34 'invoice_generated', # Factura generada
35]
36
37# Verificar cobertura
38all_event_types = set(df['event_type'].unique())
39classified = set(CORE_ACTIVE_EVENTS + BROAD_ACTIVE_EVENTS + SYSTEM_EVENTS)
40unclassified = all_event_types - classified
41
42print(f"Tipos de evento totales: {len(all_event_types)}")
43print(f"Clasificados: {len(classified)}")
44print(f"Sin clasificar: {len(unclassified)}")
45if unclassified:
46 print(f" Pendientes: {sorted(unclassified)}")

Clasificación de eventos - la base de toda métrica de actividad

Al ejecutarlo descubres que hay 8 tipos de evento sin clasificar. Algunos son obvios (task_updated, task_deleted) pero otros son ambiguos (integration_connected, template_applied). Le mandas la lista a Sandra por Slack y ella responde en 10 minutos con la clasificación completa. Trabajar con alguien que CONOCE el dominio acelera todo.

### 14:00 PM - Diseñar la tabla de eventos en Redshift

Después de comer, toca la parte de ingeniería: diseñar la tabla donde van a vivir los eventos en Redshift. Sandra te dice que ella construirá los modelos dbt encima, pero necesita que TU diseñes la tabla staging que alimenta todo. Piensas en lo que necesitas:

  • Queries de retención: cuando fue la primera y última actividad de un usuario
  • Analisis de features: que eventos hacen los que se quedan vs los que se van
  • Volumenes: cuantos eventos por día/hora/tipo para monitoring
  • Deduplicacion: event_id como clave natural para evitar duplicados de Kafka
  • Flexibilidad: el campo properties varia por tipo de evento
1-- DDL: Tabla staging de eventos de producto en Redshift
2-- Convencion de Sandra: stg_product_events
3-- El equipo downstream (Sandra con dbt) construira modelos encima
4
5CREATE TABLE staging.stg_product_events (
6 -- Identificacion del evento
7 event_id VARCHAR(32) NOT NULL, -- PK natural, dedup key
8 event_type VARCHAR(64) NOT NULL, -- task_created, board_viewed...
9 event_category VARCHAR(16) NOT NULL, -- 'core', 'broad', 'system'
10
11 -- Temporalidad
12 event_timestamp TIMESTAMP NOT NULL, -- UTC siempre
13 event_date DATE NOT NULL, -- Particion logica
14
15 -- Contexto del usuario
16 user_id VARCHAR(32) NOT NULL,
17 workspace_id VARCHAR(32) NOT NULL,
18
19 -- Propiedades frecuentes aplanadas (para queries rapidas)
20 task_id VARCHAR(32), -- NULL si no aplica
21 board_id VARCHAR(32), -- NULL si no aplica
22
23 -- JSON crudo para propiedades adicionales
24 properties_json VARCHAR(4096), -- JSON string original
25
26 -- Metadatos de ingesta (trazabilidad)
27 ingested_at TIMESTAMP DEFAULT GETDATE(),
28 source_file VARCHAR(256), -- Ruta en S3 del archivo fuente
29
30 PRIMARY KEY (event_id)
31)
32DISTSTYLE KEY
33DISTKEY (user_id) -- Queries agrupan por usuario (cohortes)
34SORTKEY (event_timestamp) -- Queries filtran por rango de fecha
35;

DDL de la tabla staging - decisiones de DISTKEY y SORTKEY explicadas

Consejo de senior: en Redshift, DISTKEY y SORTKEY son las dos decisiones más importantes de rendimiento. DISTKEY(user_id) coloca todos los eventos del mismo usuario en el mismo nodo, acelerando JOINs y GROUP BY por usuario. SORTKEY(event_timestamp) ordena fisicamente por fecha, acelerando filtros WHERE event_timestamp BETWEEN. Para eventos de producto está combinacion es casi siempre correcta.

### 15:30 PM - El imprevisto: Lucía quiere un número ya

Estás terminando de documentar el DDL cuando llega un DM de Lucía. Su estilo inconfundible:

1Lucia Fernandez 15:32 PM
2Ey, Pablo me dijo que estas con los eventos ??
3Pregunta rápida: puedes decirme cuantos usuarios ACTIVOS
4tuvimos la semana pasada? Es para una slide de mañana.
5No necesita ser exacto-exacto, un orden de magnitud.
6
7Tu 15:45 PM
8Hola Lucia! Estoy en ello. Una pregunta antes de darte
9el número: cuando dices "activos", te refieres a acciones
10core (crear/completar tareas, comentar) o cualquier uso
11incluyendo solo ver boards?
12
13Lo preguntó porque estamos definiendo la métrica con Sandra
14y queremos asegurarnos de que todos usemos el mismo número.
15
16Lucia Fernandez 15:47 PM
17Mmm buena pregunta ??
18Para la slide creo que core tiene más sentido. Los que
19DE VERDAD usan el producto, no los que solo miran.
20Cuanto te lleva?
21
22Tu 15:49 PM
23Dame 30 min, hago un conteo rápido con la muestra.
24
25Lucia Fernandez 15:49 PM
26Eres un crack ??

Fíjate: Lucía pidió "un número" y tú PREGUNTASTE la definición. Eso es exactamente lo que Sandra te dijo esta mañana. En FreshMart habrías dado el primer número que saliera del COUNT. Aquí ya sabes que un número sin definición es una bomba de relojería.

Haces el conteo rápido con Pandas. Resultado: ~8.200 usuarios core activos la semana pasada. Le mandas a Lucía el número con un caveat explícito: "Esto es un estimado basado en una muestra de 3 días extrapolada. El número exacto lo tendrá cuando el pipeline está listo (jueves)." Lucía responde con un pulgar arriba.

Lucía va a usar ese número en una presentación mañana. Si después resulta que era 6.500 en vez de 8.200 porque tu muestra no era representativa, habrás generado un problema de credibilidad. Siempre que des estimados: (1) di que es un estimado, (2) explica con qué datos lo calculaste, (3) da un rango de confianza si puedes ("entre 7K y 9K"). Lucía es razonable y lo entiende. El problema es cuando alguien DOWNSTREAM copia el número sin el caveat.

### 16:30 PM - Presentar la definición a Pablo

1#data-team | Martes 16:35 PM
2
3Tu 16:35 PM
4@Pablo @Sandra - Propuesta de definición de "usuario activo":
5
6CORE ACTIVE: usuario que en un período dado ha realizado al
7menos 1 accion con intencion:
8- task_created, task_completed, task_assigned
9- comment_added, file_uploaded
10- board_created, automation_created
11
12BROAD ACTIVE: core + acciones de consumo:
13- board_viewed, task_viewed, search_performed
14- filter_applied, notification_clicked
15- dashboard_viewed, report_exported
16
17SYSTEM: no cuenta como actividad
18- login, session_started/ended, emails, push, billing
19
20Para retención usamos CORE. Para engagement general, BROAD.
21Sandra está de acuerdo. Lucia también (le pregunte).
22
23Pablo Herrera 16:42 PM
24?? Perfecto. Me gusta que sea una definición dual.
25Documentalo en el Notion de métricas y a partir de ahora
26esa es la fuente de verdad.
27Una cosa: anade la fecha de vigencia. "Desde 2024-03-14,
28está es la definición oficial." Asi si en el futuro
29cambiamos, queda histórico.
30
31Sandra Vidal 16:44 PM
32+1 ?? Ya lo enlazo desde el schema.yml de dbt.
33Bien hecho!
34
35 ? 2 ?? 1

## ejercicios

[01]

Explorar eventos JSON con Pandas

Carga el archivo de eventos de muestra (JSON Lines) y calcula: total de eventos, tipos únicos, top 5 por volumen con porcentaje, usuarios únicos, workspaces únicos y rango temporal.

Cargando editor...
[02]

Clasificar eventos por categoría de actividad

Implementa la clasificacion acordada con Sandra: cada evento es "core", "broad" o "system". Calcula usuarios core activos en los últimos 7 días de la muestra.

Cargando editor...
[03]

Diseñar la tabla de staging de eventos en Redshift

Diseña la tabla staging.stg_product_events con event_id como PK para deduplicación, campos aplanados (task_id, board_id), JSON crudo para propiedades variables, y DISTKEY/SORTKEY optimizados para JOINs por usuario y filtros por fecha.

Cargando editor...
[04]

Documentar la definición de usuario activo

Escribe la documentación oficial de la métrica "usuario activo" siguiendo la estructura que Pablo pidió. Incluye: nombre, definición, eventos incluidos, fecha de vigencia, responsable y notas.

Cargando editor...

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...