Código Python: classificação ABC e giro de estoque

Código Python: classificação ABC e giro de estoque
Nem todo item na prateleira vale a mesma coisa. Poucos produtos carregam a maior parte do dinheiro parado em estoque e o resto apenas ocupa espaço. A classificação ABC separa essas três famílias: as que pedem vigilância quase diária, as que se revisam toda semana e as que basta olhar de vez em quando. O giro responde à outra pergunta do dia a dia: de quanto em quanto tempo um produto se renova e quantos dias de estoque estão parados. Este artigo traz um programa em Python, gratuito e sem dependências, que faz esse trabalho com o seu próprio estoque: lê um arquivo CSV, ordena os produtos pelo valor de consumo anual, separa em A, B e C, calcula o giro e os dias de estoque e deixa a lista ordenada em um arquivo que abre no Excel. Ele foi escrito para o dono da loja de materiais ou da distribuidora, para o contador que confere quanto capital está parado no depósito e para o estoquista que precisa saber em qual produto ficar de olho primeiro.
⬇ Baixar o código (ZIP)| Arquivo do ZIP | Para que serve |
|---|---|
clasificacion_abc.py | O programa completo, comentado linha por linha em português. |
datos/inventario.csv | Estoque de exemplo: dez produtos de loja de materiais com o consumo anual, o custo unitário e o estoque médio de cada um. |
salida_ejemplo.txt | A saída que você deve ver na tela ao rodar o programa, para comparar com a sua. |
LEIAME.md | O guia do pacote e os dois detalhes de método que valem dinheiro. |
O que o programa faz
- Lê o estoque de
datos/inventario.csv: nome do produto, unidades consumidas no ano, custo unitário e unidades mantidas em média no depósito. - Calcula o valor de consumo anual de cada produto multiplicando as unidades consumidas pelo custo unitário. Isso não é o que está na prateleira hoje: é o dinheiro que o produto movimenta no ano, e é essa cifra que ordena a lista inteira.
- Ordena do maior para o menor e calcula a participação de cada produto sobre o total e o acumulado, que é a soma que cresce de cima para baixo.
- Define a classe: A até oitenta por cento do acumulado, B até noventa e cinco por cento e C o resto. Existe um detalhe fino que quase todo mundo deixa passar: o produto que faz cruzar o corte já cai na classe seguinte, então a classe A fica um pouco abaixo dos oitenta por cento. Isso é normal e é bem mais útil do que forçar um número redondo.
- Calcula o giro dividindo o consumo anual pelo estoque médio. Um giro de 5 diz que o produto se renovou cinco vezes no ano; um giro de 1 avisa que ainda há mercadoria comprada há um ano na prateleira.
- Calcula os dias de estoque: os 360 dias do ano comercial divididos pelo giro. É a resposta à pergunta do chefe do depósito sobre quantos dias o estoque aguenta.
- Imprime a tabela ordenada, o resumo por classe com as porcentagens e duas conferências, e grava
salida/clasificacion_abc.csvesalida/resumen_abc.csv.
Como rodar o programa
Você só precisa do Python 3.11 ou superior instalado, com a caixa que coloca o Python no PATH marcada na instalação. Não há mais nada para instalar: o programa usa somente a biblioteca padrão, então você não mexe em gerenciador de pacotes nem em ambiente virtual.
- Baixe o ZIP pelo botão verde e descompacte em qualquer pasta.
- Abra o terminal (no Windows
cmd, no Linux ou Mac qualquer terminal) e entre nessa pasta com o comandocd. - Rode
python clasificacion_abc.py.
O programa localiza a pasta onde ele mesmo está, então você pode chamá-lo de qualquer lugar: ele sempre busca os dados e grava os resultados ao lado de si mesmo. Se aparecerem erros de codificação, confira se o CSV está salvo em UTF-8. Com o estoque de exemplo a execução leva um segundo, sem internet e sem nada mais instalado.
O código, explicado
Quatro trechos reais, copiados exatamente do programa que vem dentro do ZIP, sem nenhum ajuste.
Um: a tabela é definida como dados, não como uma impressão gigante. Cada coluna declara o nome interno, a largura e o título que aparece na tela; a partir dessa lista o programa calcula a largura da faixa e de cada separador:
# Largura fixa de cada coluna: assim a tabela nunca sai do lugar
COLUMNAS = [
("producto", 30, "Produto"),
("valor", 17, "Valor consumo"),
("participacion", 14, "Participação"),
("acumulado", 12, "Acumulado"),
("clase", 7, "Classe"),
("rotacion", 10, "Giro"),
("dias", 9, "Dias"),
]
# A largura da faixa e de cada separador da tabela
ANCHO_LINEA = sum(ancho for _, ancho, _ in COLUMNAS) + len(COLUMNAS) - 1Repare na última linha: a largura da tabela é calculada a partir das colunas. Se você acrescentar uma coluna amanhã, os separadores se ajustam sozinhos e a tabela continua alinhada.
Dois: dinheiro não se calcula com decimais binários. O programa inteiro trabalha com Decimal, e estes três ajudantes formatam os valores com separador de milhar e vírgula decimal, além das porcentagens:
def miles(valor: Decimal) -> str:
"""Formata um valor com separador de milhar e vírgula decimal (formato local)."""
entero, decimales = f"{valor:,.2f}".split(".")
return entero.replace(",", ".") + "," + decimales
def numero(valor: Decimal, decimales: int) -> str:
"""Formata um número com os decimais pedidos e o formato local."""
entero, cola = f"{valor:,.{decimales}f}".split(".")
return entero.replace(",", ".") + "," + cola
def porcentaje(valor: Decimal) -> str:
"""Formata uma porcentagem com dois decimais e o sinal de por cento."""
return numero(valor, 2) + " %"O arredondamento usa ROUND_HALF_UP, que é o que qualquer pessoa espera ao conferir uma nota à mão: os centavos sobem quando o terceiro algarismo é cinco ou mais. Nunca use números de ponto flutuante para dinheiro, porque os centavos se desviam sozinhos.
Três: o coração do cálculo. Está tudo aqui o que o artigo promete: valor de consumo, giro, dias, ordenação, acumulado e classe:
def calcular(filas: list[dict]) -> tuple[Decimal, list[dict]]:
"""Calcula valor, participação, acumulado, classe, giro e dias de cada produto."""
inventario = []
for fila in filas:
consumo = Decimal(fila["consumo_anual"]) # Unidades gastas no ano
costo = Decimal(fila["custo_unitario"]) # Quanto custa cada unidade
promedio = Decimal(fila["estoque_medio"]) # Unidades mantidas em média no depósito
# Valor de consumo: quanto vale no ano tudo o que se gasta deste produto
valor = redondear(consumo * costo)
# Giro: quantas vezes o estoque se renova no ano
rotacion = redondear(consumo / promedio) if promedio else Decimal("0.00")
# Dias de estoque: quantos dias o estoque aguenta nesse ritmo
dias = redondear(DIAS_ANO / rotacion) if rotacion else Decimal("0.00")
inventario.append({"producto": fila["produto"], "valor": valor,
"rotacion": rotacion, "dias": dias})
# Do maior valor de consumo para o menor: assim o acumulado faz sentido
inventario.sort(key=lambda item: (-item["valor"], item["producto"]))
total = sum((item["valor"] for item in inventario), Decimal("0"))
acumulado = Decimal("0")
for item in inventario:
acumulado += item["valor"]
item["participacion"] = redondear(item["valor"] * CIEN / total)
item["acumulado"] = redondear(acumulado * CIEN / total)
# Classe pelo acumulado: A até 80 por cento, B até 95 por cento e C o resto
if item["acumulado"] <= CORTE_A:
item["clase"] = "A"
elif item["acumulado"] <= CORTE_B:
item["clase"] = "B"
else:
item["clase"] = "C"
# Por isso o produto que faz cruzar o corte já cai na classe seguinte
return total, inventarioTrês coisas merecem uma olhada calma. O giro é o consumo dividido pelo estoque médio, e os dias saem da divisão do ano comercial de 360 dias por esse giro: quanto mais rápido o produto gira, menos mercadoria você precisa manter. A ordenação do maior valor para o menor é o que dá sentido ao acumulado; se a lista não estiver ordenada, o acumulado não diz nada. E a classe é decidida pelo acumulado, não pela participação isolada de cada produto, e é exatamente aí que mora a diferença entre uma classificação bem feita e uma que apenas parece bonita.
Quatro: a saída que abre no Excel. O programa grava dois arquivos CSV, e o do detalhe traz o nome do produto ao lado das cifras já calculadas:
def escribir_abc(inventario: list[dict]) -> None:
"""Salva a classificação produto por produto em um CSV."""
SALIDA_ABC.parent.mkdir(parents=True, exist_ok=True)
columnas = ["produto", "valor_consumo", "participacao",
"acumulado", "classe", "giro", "dias"]
with open(SALIDA_ABC, "w", encoding="utf-8", newline="") as archivo:
escritor = csv.writer(archivo)
escritor.writerow(columnas)
for item in inventario:
escritor.writerow([item["producto"], item["valor"], item["participacion"],
item["acumulado"], item["clase"], item["rotacion"], item["dias"]])Nos arquivos CSV as cifras usam ponto decimal, o formato que o Excel entende sem configuração, enquanto na tela o programa mostra o separador local. Assim você continua o trabalho na planilha: filtre a coluna da classe, ordene pelos dias de estoque e a sua lista de prioridades está pronta.
O que você vai ver na tela
Esta é a saída real com o estoque de exemplo que acompanha o ZIP. Primeiro a faixa do programa e a contagem de produtos lidos:
========================================================================================================= CLASSIFICAÇÃO ABC E GIRO DE ESTOQUE ========================================================================================================= Produtos lidos: 10
A tabela ordenada pelo valor de consumo anual. O exemplo fecha com um valor total de consumo anual de 332.270.000,00, e só o cimento cinza responde por 110.000.000,00, ou seja um terço do total. As porcentagens desta tabela estão escritas aqui em palavras e sem o sinal, para não brigar com o editor do blog; na tela o programa as imprime com o sinal e o CSV as guarda como números puros:
| Produto | Valor consumo | Participação (por cento) | Acumulado (por cento) | Classe | Giro | Dias |
|---|---|---|---|---|---|---|
| Cimento cinza 50 kg | 110.000.000,00 | 33,11 | 33,11 | A | 5,00 | 72,00 |
| Vergalhão 1/2 pol | 75.000.000,00 | 22,57 | 55,68 | A | 8,00 | 45,00 |
| Cabo THHN 12 AWG | 38.400.000,00 | 11,56 | 67,23 | A | 8,00 | 45,00 |
| Tinta anticorrosiva | 34.200.000,00 | 10,29 | 77,53 | A | 3,00 | 120,00 |
| Areia de pedreira m3 | 27.000.000,00 | 8,13 | 85,65 | B | 3,00 | 120,00 |
| Tubo PVC 1/2 | 21.600.000,00 | 6,50 | 92,15 | B | 3,00 | 120,00 |
| Luva de couro | 7.680.000,00 | 2,31 | 94,47 | B | 2,00 | 180,00 |
| Parafuso autoatarraxante | 6.750.000,00 | 2,03 | 96,50 | C | 1,00 | 360,00 |
| Arame doce kg | 6.240.000,00 | 1,88 | 98,37 | C | 1,50 | 240,00 |
| Telha de barro | 5.400.000,00 | 1,63 | 100,00 | C | 1,50 | 240,00 |
E este é o resumo por classe, a parte que você leva para a conversa com o dono do negócio:
| Classe | Produtos | Por cento do valor | Por cento dos itens |
|---|---|---|---|
| Classe A | 4 | 77,53 | 40,00 |
| Classe B | 3 | 16,94 | 30,00 |
| Classe C | 3 | 5,53 | 30,00 |
O fechamento mostra as duas conferências e os dois arquivos que ficam na pasta de saída:
Valor total do consumo anual: 332.270.000,00 Conferência (soma dos valores do resumo = total): 332.270.000,00 = 332.270.000,00 -> FECHA Arquivo gerado: salida/clasificacion_abc.csv Arquivo gerado: salida/resumen_abc.csv
As conferências são a parte de que os contadores mais gostam: a primeira verifica que o acumulado fecha exatamente em cem por cento e a segunda, que a soma dos valores do resumo bate com o total geral. Se alguma dissesse REVISAR em vez de FECHA, há uma cifra errada no CSV e o programa está avisando antes de você decidir em cima de números tortos.
Erros comuns e dicas
- Arquivo de dados não encontrado. O programa espera
datos/inventario.csvao lado do arquivo do programa. Se você descompactou o ZIP e depois moveu só o .py, monte a pasta de novo com a subpastadatos. - Cifras que não batem. Confira se o consumo anual está em unidades e o custo unitário em dinheiro por unidade. Misturar caixas com unidades faz o giro disparar e as classes saem de cabeça para baixo.
- Estoque médio igual a zero. Não dá para dividir por média zero: o programa deixa o valor em zero para não quebrar, mas essa informação precisa ser corrigida na origem.
- Separador decimal. Os dados de exemplo usam ponto decimal. Se o seu sistema exporta com vírgula decimal, ajuste os dados antes de rodar o programa.
- Poucos itens. Com uma lista muito curta o ABC perde o sentido, porque quase tudo cai na classe A. Use o estoque completo do negócio.
- Um único período. O valor de consumo é uma foto do último ano. Se o negócio é sazonal, rode o programa em cada trimestre e compare.
Quando isso não basta
Este programa é gratuito e serve para organizar o estoque e para entender os números: copie, altere e use com os seus dados. O que ele não faz é tocar o dia a dia, porque não registra entradas e saídas, não baixa o saldo quando alguém vende e não avisa quando um item acaba. Quando o negócio passa de poucos movimentos por dia, o arquivo e o script ficam curtos e é hora de pensar em um sistema. O Kardex Tauro existe exatamente para esse momento, para quando planilha e script já não bastam: ele mantém a ficha de estoque de cada produto, calcula o saldo e deixa o estoque em ordem. Mas antes de olhar qualquer sistema, rode este programa: com a classificação na mão você sabe quais itens justificam o software e quais não.
⬇ Baixar o código (ZIP)Baixe o ZIP, troque o estoque de exemplo pelo seu e rode o programa. Em menos tempo do que um café você vai saber quais produtos sustentam o negócio e quais estão comendo o seu capital.
Material didático de controle interno, escrito para a oficina de código deste blog. Não é consultoria contábil, tributária nem jurídica: confira as suas cifras e as normas locais antes de decidir com base nesses números.