Saltar al contenido

Indice / Practicas / EJ-5.8

EJ-5.8 · Pipeline con asserts sobre los hechos

Practica de: 5.7 Pipeline reproducibleDificultad: A

El ejercicio que cierra la unidad. 500 hechos de venta con ocho clases de defecto, que hay que resolver como un pipeline: etapas chicas encadenadas, invariantes escritas como assert y una decisión que no es descartar.

Qué hay que hacer

A partir de dw_hecho_ventas.csv, producir:

Y hay un requisito de forma, no solo de resultado: escribilo como un pipeline de funciones puras encadenadas con pipe, con los assert del contrato al final. Podés llegar al mismo archivo de otra manera, pero el ejercicio es este.

ELI5Explicado facil

Es una línea de control de calidad en una fábrica. Cada estación revisa una cosa y le pega una etiqueta a lo que está mal - nadie tira nada al piso en el medio.

Al final de la cinta hay dos cajas: lo que llegó sin etiqueta y lo que llegó con una. Como nadie tiró nada por el camino, las dos cajas juntas tienen exactamente lo que entró, y eso se puede verificar contando.

Archivos

Las cuatro decisiones que hay que tomar

  1. Qué se rechaza. Nueve reglas, y una fila puede fallar varias: el contrato fija el orden, así que cada rechazo aparece una sola vez.
  2. Qué se marca sin rechazar. Los ingresos atípicos se conservan con atipico = 1. Son raros, no imposibles.
  3. Cuándo se mide la dispersión. El límite del IQR se calcula después de sacar lo imposible. Medido antes, un ingreso de siete dígitos estira el rango y arrastra decenas de filas normales.
  4. Qué promete el pipeline. Seis assert, que son las invariantes del contrato escritas en el código.

Cómo saber si te salió

python comparar.py mi_validos.csv hechos_validos.csv
python comparar.py mi_rechazados.csv hechos_rechazados.csv

Antes de arrancar, mirá los datos

dw_hecho_ventas.csv (entrada) · 500 filas, se muestran 5
venta_idfechaproducto_idcliente_idcantidadingresocosto
12026-01-1325221810115.147375.11
22026-05-26282842456.661778.94
32026-06-052729172041.281072.75
42026-01-13427216084.274549.38
52026-07-311924199774.038178.44

El contrato de salida

hechos_validos.csv · 490 filas

ColumnaTipoQue lleva
venta_identeroEl id original de la venta
fechatextoAAAA-MM-DD, ya validada contra el calendario
producto_identeroEntre 1 y 30
cliente_identeroEntre 1 y 40
cantidadenteroMayor que 0
ingresodecimalMayor que 0, redondeado a 2 decimales
costodecimalRedondeado a 2 decimales
margendecimalingreso menos costo, redondeado a 2 decimales. Nunca negativo
atipicoentero1 si el ingreso supera el limite IQR, 0 si no. NO se descarta: se marca

Orden de las filas: venta_id ascendente. Es unico, asi que no hace falta desempate. Indice: no se escribe: to_csv(index=False)

hechos_rechazados.csv · 10 filas

ColumnaTipoQue lleva
venta_identeroEl id de la venta descartada
columnatextoEn que columna estaba el problema
valor_originaltextoEl valor tal cual venia en el archivo
motivotextoUno del vocabulario cerrado del curso

Orden de las filas: venta_id ascendente. Indice: no se escribe: to_csv(index=False)

Que hacer con cada defecto

DefectoDondeAccionAdonde va
fecha vacia1 fila, venta_id 402descartar descartarhechos_rechazados.csv con motivo ausente
fecha que no existe en el calendario (2026-02-31)1 fila, venta_id 455descartar descartarhechos_rechazados.csv con motivo fecha_invalida
cantidad ausente1 fila, venta_id 128descartar descartarhechos_rechazados.csv con motivo ausente
cantidad 0 o negativa (0 y -6)2 filas, venta_id 44 y 91descartar descartarhechos_rechazados.csv con motivo fuera_de_rango
costo ausente: campo vacio y token N/A2 filas, venta_id 213 y 266descartar descartarhechos_rechazados.csv con motivo ausente
ingreso 0 o negativo (-320.0)1 fila, venta_id 177descartar descartarhechos_rechazados.csv con motivo fuera_de_rango
producto_id 777, que no existe en el catalogo de 30 productos1 fila, venta_id 305descartar descartarhechos_rechazados.csv con motivo huerfano
cliente_id 888, que no existe en el padron de 40 clientes1 fila, venta_id 351descartar descartarhechos_rechazados.csv con motivo huerfano
ingreso menor que el costo (venta a perdida)la unica fila que cumplia esto ya se rechazo antes por ingreso negativo, asi que este motivo no aparece en el informedescartar. descartar. La regla va igual: sin ella, el assert de margen no negativo no estaria garantizado el mes que vienehechos_rechazados.csv con motivo negativo_incoherente
ingreso de 9999999.99 y otros 14 por encima del limite IQR15 filasMARCAR MARCAR con atipico = 1 y conservar. Es la unica clase de defecto que no descarta nadahechos_validos.csv
Orden de comprobacion

Una fila puede tener mas de un problema. Se informa solo el primero de esta lista:

  1. fecha ausente
  2. fecha invalida
  3. cantidad ausente
  4. cantidad menor o igual que 0
  5. costo ausente
  6. ingreso ausente
  7. ingreso menor o igual que 0
  8. producto_id fuera de 1 a 30
  9. cliente_id fuera de 1 a 40
  10. ingreso menor que costo

Se informa SOLO el primer motivo de la lista, para que cada fila rechazada aparezca una vez y valga validos + rechazados = 500. El orden importa de verdad en un caso: la venta 177 tiene ingreso -320, que es a la vez un ingreso no positivo y una venta por debajo del costo; se informa como fuera_de_rango porque esa regla va antes.

Conteos clave

Si estos numeros no te dan, algo se desvio antes de que llegues a comparar los archivos.

filas de entrada500
hechos_validos.csv490
hechos_rechazados.csv10
atipicos marcados (no descartados)15
limite IQR del ingreso12432.61
por motivoausente: 4, fuera_de_rango: 3, huerfano: 2, fecha_invalida: 1, negativo_incoherente: 0

Invariantes

Comprobaciones que tu resultado tiene que cumplir. Corrilas vos mismo antes de comparar los archivos:

len(validos) + len(rechazados) == 500
validos['venta_id'].is_unique
(validos['cantidad'] > 0).all()
(validos['ingreso'] > 0).all()
(validos['margen'] >= 0).all()
validos['producto_id'].between(1, 30).all()
validos['cliente_id'].between(1, 40).all()
validos.notna().all().all()
int(validos['atipico'].sum()) == 15
rechazados['venta_id'].is_unique
set(rechazados['motivo']) <= {'ausente', 'fecha_invalida', 'fuera_de_rango', 'huerfano', 'negativo_incoherente'}
PISTASSi te trabas, abri esto
  • Escribilo como pipeline: una funcion por etapa, que recibe una tabla y devuelve una tabla. Ninguna saca filas; todas anotan el motivo en una columna. Se parte en dos al final.
  • El motivo se anota solo donde no habia otro. Sin esa condicion, una fila con dos defectos aparece dos veces y la conciliacion deja de cerrar.
  • El IQR se calcula sobre los ingresos que quedaron. Calculado sobre los 500, el -320 y el de siete digitos deforman los cuartiles y el limite sale mal.
  • Los asserts son parte del entregable, no un adorno: si el mes que viene el archivo trae un caso nuevo, son lo unico que te va a avisar antes de que el numero llegue a un informe.

Así tiene que quedar

hechos_validos.csv (objetivo) · 490 filas, se muestran 5
venta_idfechaproducto_idcliente_idcantidadingresocostomargenatipico
12026-01-1325221810115.147375.112740.030
22026-05-26282842456.661778.94677.720
32026-06-052729172041.281072.75968.530
42026-01-13427216084.274549.381534.890
52026-07-311924199774.038178.441595.590
hechos_rechazados.csv (objetivo) · 10 filas, se muestran 5
venta_idcolumnavalor_originalmotivo
44cantidad0fuera_de_rango
91cantidad-6fuera_de_rango
128cantidadvacioausente
177ingreso-320.0fuera_de_rango
213costovacioausente
Una regla que no rechaza nada, y va igual

La regla «ingreso menor que costo» no aparece en el informe: la única fila que la cumplía ya se había rechazado antes por tener el ingreso negativo. Y sin embargo el contrato la pide.

El motivo es que uno de los assert promete que margen nunca es negativo. Hoy eso se cumple de casualidad; sin la regla, el archivo del mes que viene puede traer una venta a pérdida legítima y el assert va a explotar sin que nadie sepa por qué. Una regla que hoy captura cero filas no es una regla de más: es la que sostiene la promesa.

El número que hay que poder explicar

Las 15 filas marcadas como atípicas se quedan en el archivo, y entre todas explican el 85 % de los ingresos. Ese es exactamente el motivo de marcarlas en vez de descartarlas en silencio: cualquier total calculado sobre este archivo tiene que poder decir si las incluye o no.

Con la bandera, contestar esa pregunta es un groupby. Sin ella, hay que rehacer todo el trabajo.