Saltar al contenido

Indice / Practicas / EJ-1.5

EJ-1.5 · Inventario saneado con informe

Practica de: 1.6 Informe de rechazos y loggingDificultad: M

La práctica de cierre de la unidad. Además del CSV limpio hay que producir el informe: el archivo que lee una persona para decidir si confía en el otro.

Qué hay que hacer

A partir de inventario_productos.csv (35 filas), producir dos archivos:

Sin pandas. El informe se arma con f-strings y se compara línea por línea, así que los anchos de columna son parte del contrato.

ELI5Explicado facil

Es el recuento del depósito. Al final del día no alcanza con la planilla nueva: también hay que dejar el papelito que dice «faltaron seis cajas, y estas son».

Sin ese papelito, el que mire la planilla mañana no tiene forma de saber si están todas.

Archivos

El defecto que no parece un defecto

P023 tiene 999.999 unidades en stock

Es un número perfectamente válido: ninguna conversión falla, no es negativo, no está vacío. Y se lleva el 99,6 % del valor total del inventario.

Lo que lo rechaza no está en los datos: es una regla del negocio - el depósito no tiene lugar para más de 10.000 unidades de un producto - y por eso está escrita en el contrato. Sin ella, el informe sería técnicamente correcto y completamente inútil.

Los seis rechazos

FilaProductoQué pasaMotivo
4P004cantidad vacíaausente
7P007cantidad = -5negativo_incoherente
11P011precio_unitario = NaNausente
15P015precio_unitario = -12.3negativo_incoherente
23P023cantidad = 999999atipico
30P030precio_unitario = N/Aausente

Ninguno levanta una excepción por sí solo: NaN y N/A hay que buscarlos con la lista de tokens, y el negativo y el atípico son números válidos.

Cómo saber si te salió

python comparar.py mi_saneado.csv inventario_saneado.csv

El informe de texto no lo compara comparar.py: se mira contra el extracto de esta página, o con un diff.

Antes de arrancar, mirá los datos

inventario_productos.csv (entrada) · 35 filas, se muestran 5
codigoproductocategoriacantidadprecio_unitario
P001Teclado mecánicoPerifericos1124.53
P002Ratón inalámbricoPerifericos82781.78
P003Monitor 24 pulgadasPantallas108872.23
P004Monitor 27 pulgadasPantallasvacio432.95
P005Webcam HDPerifericos26713.42

El contrato de salida

inventario_saneado.csv · 29 filas

ColumnaTipoQue lleva
codigotextoEl codigo tal cual vino
productotextoEl nombre del producto tal cual vino
categoriatextoLa categoria tal cual vino
cantidadenteroLas unidades en stock, como entero
precio_unitariodecimalEl precio, 2 decimales
valor_stockdecimalcantidad * precio_unitario, redondeado a 2 decimales

Orden de las filas: codigo ascendente. Los 35 codigos del archivo son distintos, asi que el orden es total y no hace falta desempate. Indice: no se escribe: csv.DictWriter no agrega ninguna columna de indice, y con pandas seria to_csv(index=False)

informe_inventario.txt · 25 filas

ColumnaTipoQue lleva

Orden de las filas: El orden es el del formato, no una decision: los motivos van alfabeticos y el detalle en el orden en que aparecen las filas en el archivo de entrada. Indice: no aplica: es texto plano, no una tabla. Con pandas la equivalencia seria to_csv(index=False), pero aca se escribe con f-strings

Que hacer con cada defecto

DefectoDondeAccionAdonde va
cantidad ausentefila 4, producto P004rechazar rechazar la filainforme_inventario.txt, motivo ausente
precio_unitario con los tokens NaN y N/Afilas 11 y 30, productos P011 y P030tratarlos tratarlos como ausentes: la lista es NULOS = {'', '-', 'N/A', 'NaN', 'null'}informe_inventario.txt, motivo ausente
cantidad o precio negativosfilas 7 y 15: P007 con -5 unidades y P015 con precio -12.3rechazar rechazar la fila. Un stock negativo y un precio negativo son imposibles, no atipicosinforme_inventario.txt, motivo negativo_incoherente
cantidad por encima del techo de depositofila 23, producto P023 con 999999 unidadesrechazar rechazar la fila. El techo es 10000 unidades y lo fija el enunciado, no los datosinforme_inventario.txt, motivo atipico
cantidad o precio no numericos0 filas en este archivola la comprobacion va igual: los tokens de ausente ya cubren lo que hay, pero un precio escrito con letras entraria por acainforme_inventario.txt, motivo no_numerico
Orden de comprobacion

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

  1. ausente en codigo, producto o categoria
  2. ausente en cantidad o precio_unitario
  3. no numerico en cantidad o precio_unitario
  4. negativo en cantidad o precio_unitario
  5. cantidad por encima de 10000

Una fila se rechaza UNA vez, con el primer defecto que aparece, y por eso el conteo por motivo suma exactamente las filas rechazadas. El techo va ultimo porque necesita que la cantidad ya sea un numero valido y no negativo.

Conteos clave

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

filas de entrada35
inventario_saneado.csv29
informe_inventario.txt25
filas rechazadas6
valor de stock total1177401.73
unidades en stock2098
por motivoausente: 3, negativo_incoherente: 2, atipico: 1

Invariantes

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

len(saneado) == 29
len(saneado) + 6 == 35
saneado['codigo'].is_unique
saneado['codigo'].is_monotonic_increasing
not saneado.isna().any().any()
(saneado['cantidad'] > 0).all()
(saneado['cantidad'] <= 10000).all()
(saneado['precio_unitario'] > 0).all()
(saneado['valor_stock'] - saneado['cantidad'] * saneado['precio_unitario']).abs().max() < 0.005
abs(saneado['valor_stock'].sum() - 1177401.73) < 0.01
saneado['cantidad'].sum() == 2098
PISTASSi te trabas, abri esto
  • Las 999999 unidades de P023 son un numero perfectamente valido: ninguna conversion falla. El techo de 10000 lo pone el enunciado, y sin el ese producto se lleva el 99,6% del valor de stock.
  • El techo se revisa DESPUES del negativo, o un -5 tambien tendria que compararse contra 10000 y el motivo saldria equivocado.
  • El informe se compara linea por linea, asi que los anchos importan. Salen todos de f-strings con formato: f'{motivo:<22} {cantidad:>3}'.
  • El conteo por motivo tiene que sumar 6, que es la cantidad de filas rechazadas. Si suma mas, alguna fila se conto dos veces: cada fila se rechaza una sola vez.

Así tienen que quedar

inventario_saneado.csv (objetivo) · 29 filas, se muestran 5
codigoproductocategoriacantidadprecio_unitariovalor_stock
P001Teclado mecánicoPerifericos1124.53507.36
P002Ratón inalámbricoPerifericos82781.7864105.96
P003Monitor 24 pulgadasPantallas108872.2394200.84
P005Webcam HDPerifericos26713.4218548.92
P006AuricularesAudio56542.830396.8
informe_inventario.txt (objetivo) · 27 filas, se muestran 5
INFORME DE INVENTARIO
==============================================
archivo : inventario_productos.csv
filas leidas : 35
filas saneadas : 29
filas rechazadas : 6
El orden de las comprobaciones decide el motivo

El techo de 10.000 se revisa después del negativo. Si fuera al revés, un -5 también se compararía contra el techo y el motivo saldría equivocado.

Y cada fila se rechaza una sola vez: por eso el desglose por motivo suma exactamente 6, que es la cantidad de filas rechazadas. Si te suma más, alguna fila se contó dos veces.

Los anchos del informe

Salen todos de f-strings con formato: f"{motivo:<22} {cantidad:>3}" para el desglose, f"{numero:>4} {codigo:<8} {columna:<16}" para el detalle. Los separadores son 46 caracteres.

El valor de stock va con dos decimales y sin separador de miles: 1177401.73. Un punto que separa miles y un punto que separa decimales en el mismo archivo es la receta de la lección 7.1 para arruinar una columna.