Todos os estudos de caso

Logística e Supply Chain

Torre de controle logística: OTIF, estoque e custo de frete

Contexto

Operador logístico com 4 centros de distribuição, 180 rotas semanais e 12 mil SKUs armazenados para 30 embarcadores. Os indicadores existiam, mas cada CD calculava OTIF de um jeito. O objetivo é uma definição única, um painel diário para a operação e um painel mensal para a diretoria e para os clientes.

Perguntas que o painel deve responder

  • Quais clientes estão abaixo do OTIF contratado e por qual causa?
  • Quais SKUs estão em ruptura e quais estão obsoletos?
  • Qual rota tem o maior custo por entrega e por quê?
  • O atraso é do CD, da transportadora ou do cliente que não recebeu?
  • Quanto de estoque parado eu tenho hoje, em reais?

Indicadores e metas

IndicadorMetaPor que importa
OTIF (On Time In Full)≥ 95%O indicador que o cliente enxerga; combina prazo e completude
On Time %≥ 97%Separa o problema de prazo do problema de disponibilidade
In Full %≥ 98%Mede ruptura e erro de separação
Giro de Estoque≥ 9x/anoEstoque parado é capital imobilizado e risco de obsolescência
Cobertura em Dias30 a 45 diasTraduz o giro em linguagem de planejamento
Acuracidade de Inventário≥ 99,5%Sem acuracidade, todo o resto do painel é ficção
Custo de Frete sobre Receita≤ 6,5%Principal linha de custo variável da operação
Ocupação de Veículo≥ 85%Frete pago por espaço vazio é perda pura

Modelo de dados

Logística tem múltiplas fatos com granularidades diferentes: pedido, item do pedido, embarque e posição de estoque. Cada uma se relaciona com as dimensões compartilhadas, nunca entre si.

TabelaGranularidadeDatas relevantes
fPedidosPedidoData do pedido, prometida, expedição, entrega
fItensPedidoItem do pedidoQuantidade pedida x separada x entregue
fEmbarquesConhecimento de transporteColeta, entrega, peso, cubagem, valor do frete
fEstoquePosicaoDia × CD × SKUSaldo, custo médio, data da última movimentação
fInventarioContagem × SKUSaldo sistema x saldo contado
dCliente / dCD / dProduto / dTransportadora / dRotaDimensões—
dCalendarioDiaCom marcação de dia útil e feriado por estado

Medidas DAX

OTIF e seus componentes

DAX
Pedidos Entregues = DISTINCTCOUNT( fPedidos[NumeroPedido] ) On Time =CALCULATE(    [Pedidos Entregues],    FILTER( fPedidos, fPedidos[DataEntrega] <= fPedidos[DataPrometida] )) In Full =CALCULATE(    [Pedidos Entregues],    FILTER(        fPedidos,        NOT EXISTS(            FILTER(                RELATEDTABLE( fItensPedido ),                fItensPedido[QtdEntregue] < fItensPedido[QtdPedida]            )        )    )) OTIF =CALCULATE(    [Pedidos Entregues],    FILTER(        fPedidos,        fPedidos[DataEntrega] <= fPedidos[DataPrometida] &&        fPedidos[QtdItensDivergentes] = 0    )) OTIF % = DIVIDE( [OTIF], [Pedidos Entregues] )On Time % = DIVIDE( [On Time], [Pedidos Entregues] )In Full % = DIVIDE( [In Full], [Pedidos Entregues] )

OTIF não é On Time × In Full. É o pedido que cumpriu as duas condições simultaneamente — por isso é sempre menor ou igual ao menor dos dois.

Atraso e sua causa

DAX
Dias de Atraso Médio =AVERAGEX(    FILTER( fPedidos, fPedidos[DataEntrega] > fPedidos[DataPrometida] ),    DATEDIFF( fPedidos[DataPrometida], fPedidos[DataEntrega], DAY )) Causa do Atraso =VAR AtrasoSeparacao =    CALCULATE( COUNTROWS( fPedidos ), FILTER( fPedidos, fPedidos[DataExpedicao] > fPedidos[DataPrometidaExpedicao] ) )VAR AtrasoTransporte =    CALCULATE(        COUNTROWS( fPedidos ),        FILTER( fPedidos,            fPedidos[DataExpedicao] <= fPedidos[DataPrometidaExpedicao] &&            fPedidos[DataEntrega] > fPedidos[DataPrometida] )    )RETURNSWITCH( TRUE(),    AtrasoSeparacao > AtrasoTransporte, "CD / Separação",    AtrasoTransporte > 0, "Transporte",    "Sem atraso")

Separar a causa é o que transforma o OTIF de indicador de cobrança em ferramenta de melhoria: o CD e a transportadora param de culpar um ao outro.

Giro, cobertura e estoque parado

DAX
Estoque Médio =AVERAGEX( VALUES( dCalendario[Date] ), CALCULATE( SUM( fEstoquePosicao[ValorEstoque] ) ) ) CMV Período = CALCULATE( SUM( fItensPedido[CustoTotal] ) ) Giro de Estoque =VAR DiasPeriodo = COUNTROWS( dCalendario )RETURN DIVIDE( [CMV Período], [Estoque Médio] ) * DIVIDE( 365, DiasPeriodo ) Cobertura em Dias = DIVIDE( 365, [Giro de Estoque] ) Estoque Parado =CALCULATE(    SUM( fEstoquePosicao[ValorEstoque] ),    FILTER(        fEstoquePosicao,        DATEDIFF( fEstoquePosicao[DataUltimaSaida], TODAY(), DAY ) > 120    )) Estoque Parado % = DIVIDE( [Estoque Parado], [Valor Total em Estoque] )

Giro precisa ser anualizado para ser comparável entre períodos de tamanhos diferentes. Cobertura é o mesmo número em linguagem de planejador.

Acuracidade de inventário

DAX
Acuracidade % =VAR SKUsContados = DISTINCTCOUNT( fInventario[IdProduto] )VAR SKUsCorretos =    CALCULATE(        DISTINCTCOUNT( fInventario[IdProduto] ),        FILTER( fInventario, fInventario[SaldoSistema] = fInventario[SaldoContado] )    )RETURN DIVIDE( SKUsCorretos, SKUsContados ) Acuracidade Financeira % =VAR DivergenciaAbs =    SUMX( fInventario, ABS( fInventario[SaldoSistema] - fInventario[SaldoContado] ) * fInventario[CustoUnitario] )RETURN 1 - DIVIDE( DivergenciaAbs, [Valor Total em Estoque] )

Acuracidade por SKU e acuracidade financeira contam histórias diferentes: errar 200 parafusos não é o mesmo que errar 2 motores.

Custo de frete e ocupação

DAX
Custo de Frete = SUM( fEmbarques[ValorFrete] ) Frete sobre Receita % = DIVIDE( [Custo de Frete], [Receita de Vendas] ) Custo por Entrega = DIVIDE( [Custo de Frete], DISTINCTCOUNT( fEmbarques[IdEntrega] ) ) Custo por Kg = DIVIDE( [Custo de Frete], SUM( fEmbarques[PesoKg] ) ) Ocupação de Veículo % =AVERAGEX(    VALUES( fEmbarques[IdViagem] ),    VAR PesoUtil = CALCULATE( SUM( fEmbarques[PesoKg] ) )    VAR Capacidade = CALCULATE( MAX( dVeiculo[CapacidadeKg] ) )    VAR CubUtil = CALCULATE( SUM( fEmbarques[CubagemM3] ) )    VAR CapCub = CALCULATE( MAX( dVeiculo[CapacidadeM3] ) )    RETURN MAX( DIVIDE( PesoUtil, Capacidade ), DIVIDE( CubUtil, CapCub ) ))

Ocupação é o maior entre peso e cubagem: carga leve e volumosa lota o caminhão sem atingir o peso máximo.

Classificação XYZ (previsibilidade de demanda)

DAX
Coef. Variação Demanda =VAR Serie =    ADDCOLUMNS( VALUES( dCalendario[AnoMes] ), "@Qtd", [Quantidade Vendida] )VAR Media  = AVERAGEX( Serie, [@Qtd] )VAR Desvio =    SQRT( DIVIDE( SUMX( Serie, ( [@Qtd] - Media ) ^ 2 ), COUNTROWS( Serie ) - 1 ) )RETURN DIVIDE( Desvio, Media ) Classe XYZ =SWITCH( TRUE(),    [Coef. Variação Demanda] <= 0.25, "X - Previsível",    [Coef. Variação Demanda] <= 0.60, "Y - Variável",    "Z - Errático")

Cruzar ABC (valor) com XYZ (previsibilidade) gera 9 estratégias de estoque distintas — é a análise de maior retorno em supply chain.

Layout do painel

  1. 1Página 1 — Torre de Controle (operação, atualizada de hora em hora): cartões de OTIF do dia, pedidos em atraso, pedidos a expedir, veículos em rota; tabela de pedidos críticos com semáforo e link para o WMS.
  2. 2Página 2 — OTIF Gerencial: linha de OTIF mensal com meta; decomposição em On Time e In Full; barras por causa de atraso; matriz por cliente com OTIF contratado x realizado.
  3. 3Página 3 — Estoque: matriz ABC × XYZ com valor em estoque em cada célula; barras de estoque parado por faixa de dias; giro e cobertura por CD; acuracidade por contagem.
  4. 4Página 4 — Custo: custo por entrega e por kg por rota; ocupação média por transportadora; dispersão distância × custo revelando rotas fora da curva.
  5. 5Tooltip de página em toda matriz de cliente: evolução de 12 meses do OTIF daquele cliente.
  6. 6Drill-through por rota: detalhe de cada viagem com peso, cubagem, ocupação e custo.

Desafio final

Implemente OTIF com a separação de causa e valide a definição com a operação dos 4 CDs. Depois construa a matriz ABC × XYZ e proponha uma política de estoque distinta para cada uma das 9 células.