Indice / Practicas / EJ-1.5
EJ-1.5 · Inventario saneado con informe
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:
inventario_saneado.csv- los 29 productos utilizables, con una columna calculada:valor_stock = cantidad × precio_unitario.informe_inventario.txt- el informe en texto plano, con los totales, el desglose por motivo y el detalle de las 6 filas rechazadas.
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
- Entrada ·
inventario_productos.csv - Objetivo ·
inventario_saneado.csv - Objetivo ·
informe_inventario.txt - Herramienta ·
comparar.py
El defecto que no parece un defecto
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
| Fila | Producto | Qué pasa | Motivo |
|---|---|---|---|
| 4 | P004 | cantidad vacía | ausente |
| 7 | P007 | cantidad = -5 | negativo_incoherente |
| 11 | P011 | precio_unitario = NaN | ausente |
| 15 | P015 | precio_unitario = -12.3 | negativo_incoherente |
| 23 | P023 | cantidad = 999999 | atipico |
| 30 | P030 | precio_unitario = N/A | ausente |
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
| codigo | producto | categoria | cantidad | precio_unitario |
|---|---|---|---|---|
| P001 | Teclado mecánico | Perifericos | 112 | 4.53 |
| P002 | Ratón inalámbrico | Perifericos | 82 | 781.78 |
| P003 | Monitor 24 pulgadas | Pantallas | 108 | 872.23 |
| P004 | Monitor 27 pulgadas | Pantallas | vacio | 432.95 |
| P005 | Webcam HD | Perifericos | 26 | 713.42 |
El contrato de salida
inventario_saneado.csv · 29 filas
| Columna | Tipo | Que lleva |
|---|---|---|
codigo | texto | El codigo tal cual vino |
producto | texto | El nombre del producto tal cual vino |
categoria | texto | La categoria tal cual vino |
cantidad | entero | Las unidades en stock, como entero |
precio_unitario | decimal | El precio, 2 decimales |
valor_stock | decimal | cantidad * precio_unitario, redondeado a 2 decimales |
informe_inventario.txt · 25 filas
| Columna | Tipo | Que lleva |
|---|
Que hacer con cada defecto
| Defecto | Donde | Accion | Adonde va |
|---|---|---|---|
| cantidad ausente | fila 4, producto P004 | rechazar rechazar la fila | informe_inventario.txt, motivo ausente |
| precio_unitario con los tokens NaN y N/A | filas 11 y 30, productos P011 y P030 | tratarlos tratarlos como ausentes: la lista es NULOS = {'', '-', 'N/A', 'NaN', 'null'} | informe_inventario.txt, motivo ausente |
| cantidad o precio negativos | filas 7 y 15: P007 con -5 unidades y P015 con precio -12.3 | rechazar rechazar la fila. Un stock negativo y un precio negativo son imposibles, no atipicos | informe_inventario.txt, motivo negativo_incoherente |
| cantidad por encima del techo de deposito | fila 23, producto P023 con 999999 unidades | rechazar rechazar la fila. El techo es 10000 unidades y lo fija el enunciado, no los datos | informe_inventario.txt, motivo atipico |
| cantidad o precio no numericos | 0 filas en este archivo | la la comprobacion va igual: los tokens de ausente ya cubren lo que hay, pero un precio escrito con letras entraria por aca | informe_inventario.txt, motivo no_numerico |
Una fila puede tener mas de un problema. Se informa solo el primero de esta lista:
- ausente en codigo, producto o categoria
- ausente en cantidad o precio_unitario
- no numerico en cantidad o precio_unitario
- negativo en cantidad o precio_unitario
- 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
| filas de entrada | 35 |
|---|---|
| inventario_saneado.csv | 29 |
| informe_inventario.txt | 25 |
| filas rechazadas | 6 |
| valor de stock total | 1177401.73 |
| unidades en stock | 2098 |
| por motivo | ausente: 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
| codigo | producto | categoria | cantidad | precio_unitario | valor_stock |
|---|---|---|---|---|---|
| P001 | Teclado mecánico | Perifericos | 112 | 4.53 | 507.36 |
| P002 | Ratón inalámbrico | Perifericos | 82 | 781.78 | 64105.96 |
| P003 | Monitor 24 pulgadas | Pantallas | 108 | 872.23 | 94200.84 |
| P005 | Webcam HD | Perifericos | 26 | 713.42 | 18548.92 |
| P006 | Auriculares | Audio | 56 | 542.8 | 30396.8 |
| INFORME DE INVENTARIO |
|---|
| ============================================== |
| archivo : inventario_productos.csv |
| filas leidas : 35 |
| filas saneadas : 29 |
| filas rechazadas : 6 |
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.
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.