Capstone · Insper

Necessidade de Abastecimento

Do ponto de venda ao centro de distribuição — um pipeline de dados para decidir quanto repor, onde e quando, na linha high-end OPPO da rede Claro.

Guilherme Fidalgo Velloso Ferreira
Programa Avançado em Data Science e Decisão · Insper
Agenda

O que vamos percorrer

  1. Contexto de negócio — o problema de abastecimento
  2. Regras de negócio — como a operação pensa reposição
  3. Arquitetura técnica — por que um pipeline Kedro
  4. O pipeline, etapa por etapa
  5. O que sai do pipeline
  6. Decisões de engenharia e próximos passos
Parte 1

Contexto de negócio

Contexto

OPPO vende através da rede Claro

O problema

Quanto essa loja precisa receber agora?

Toda semana, alguém precisa responder essa pergunta — loja a loja, modelo a modelo.

Errar para baixo

Ruptura. Loja sem produto, venda perdida, cliente migra para o concorrente.

Errar para cima

Capital parado. Estoque de giro lento imobilizado na loja errada.

Hoje essa decisão depende de planilhas manuais — difíceis de auditar e de repetir toda semana com os mesmos critérios.

Segmentação da rede

Store tier — cobertura alvo por potencial

Tier Perfil Cobertura alvo (DOS)
S Loja de maior potencial 30 dias
A Loja de potencial alto 30 dias
B Loja de potencial padrão 21 dias

A dimensão cluster OPPO define quais modelos cada loja pode vender (flags OPPO 1OPPO 4, NOT CLUSTER no cadastro de produtos). Uma loja do cluster 4 não é candidata a receber um modelo reservado ao cluster 2.

Regra de negócio

Cobertura ideal de estoque

DOS (Days of Supply) — quantos dias o estoque atual sustenta o ritmo de vendas da loja.

estoque ideal = DOS ideal do tier × velocidade de vendas
necessidade de abastecimento = max(0, estoque ideal − estoque projetado)

Simples na teoria — a dificuldade real está em medir velocidade de vendas de forma confiável.

O problema escondido

Ruptura distorce a demanda observada

Loja fica 3 dos últimos 7 dias sem estoque, e ainda assim vende 4 unidades:

0,57un/dia — vendas ÷ 7 dias corridos
(subestima a demanda)
0,80un/dia — vendas ÷ dias com estoque
(demanda real)

O pipeline sempre calcula velocidade por dia com produto disponível na prateleira. Loja sem estoque não é loja sem demanda.

E sem histórico suficiente?

Cascata de fallback

Loja nova, modelo recém-lançado, ou poucos dias com estoque na janela — a amostra não é confiável.

  1. Própria loja, se teve dias suficientes com estoque disponível
  2. Grupo de pares — mesma combinação cluster OPPO + tier + modelo
  3. Modelo, agrupado em toda a base (último recurso)

Evita que uma loja com 1 dia de estoque e 1 venda vire “vende 1 unidade por dia” — erro clássico de extrapolação com amostra pequena.

Parte 2

Da regra de negócio ao pipeline

parameters_data_processing.yml

Regra de negócio vira configuração, não código

O time ajusta tiers, cobertura alvo ou pesos sem tocar em lógica Python.

tiers_doi:
  S: 30
  A: 30
  B: 21

peso_vel_a: 0.6 # peso da velocidade de 7 dias (curto prazo, reativo)
peso_vel_b: 0.4 # peso da velocidade de 30 dias (estável, menos ruído)

min_dias_disponiveis_a: 5 # mínimo de dias com estoque p/ confiar na janela de 7d
min_dias_disponiveis_b: 21 # idem, janela de 30d
Por que Kedro

Não um notebook único

Notebook único

  • Lógica e parâmetros misturados no código
  • Ordem de execução implícita, difícil de auditar
  • Reprocessa tudo do zero a cada mudança
  • Difícil testar uma etapa isolada

Pipeline Kedro

  • Regras isoladas em parameters_data_processing.yml
  • Grafo de dependências explícito em pipeline.py
  • Cada etapa cacheada como dataset — reroda só o necessário
  • Cada nó é uma função pura, testável isoladamente

Camadas de dado seguem a convenção do Kedro: 01_raw → 02_intermediate → 03_primary → 04_feature → 05_model_input → 07_model_output.

catalog.yml

O catálogo de dados

Cada dataset é declarado — formato, caminho, e como ler os arquivos brutos.

estoques_claro_folder:
  type: partitions.PartitionedDataset
  path: data/01_raw/claro/estoques
  dataset:
    type: pandas.CSVDataset
    load_args:
      sep: ";"
      encoding: "latin-1"

A Claro entrega estoque e vendas como um CSV por dia. O PartitionedDataset lê a pasta inteira automaticamente — nenhum código muda quando um novo dia de arquivo chega.

pipeline.py

O pipeline completo

Kedro Pipeline

Etapa 1 · criar_base

Universo válido — loja × modelo

Cruza cada loja com os produtos do seu cluster.

cluster_col = cluster_map.get(cluster, None)          # "OPPO 2" -> "cluster_2"
modelos = produtos[produtos[cluster_col] == 'Y']['modelo']

A combinação loja × modelo só existe se aquele modelo é vendido naquele cluster — evita recomendar um Find X9 Pro para uma loja que nunca deveria vendê-lo. criar_base_cds faz o mesmo para os CDs, com produto cartesiano completo.

Etapa 2 · criar_base_diaria

Construindo o painel diário

Grade loja × modelo × dia dos últimos 35 dias, cruzada com vendas e estoque reportados.

Etapa 3 · o núcleo do pipeline

Velocidade com fallback

loja_confiavel = velocidade['dias_disponiveis'] >= min_dias_disponiveis

# Nivel 1: pooled por cluster_oppo + store_tier + modelo
peer = velocidade.groupby(['cluster_oppo','store_tier','modelo']).agg(...)

# Nivel 2 (ultimo recurso): pooled por modelo em toda a base
modelo_pool = velocidade.groupby('modelo').agg(...)

velocidade = where(loja_confiavel, vel_propria, vel_peer).fillna(vel_modelo)

Rodado duas vezes — janela de 7 dias (reativo) e 30 dias (estável) — e combinado com peso 0,6 / 0,4.

Etapa 4 · calc_necessidade_abastecimento

A fórmula final

velocidade_vendas = vel_7d * 0.6 + vel_30d * 0.4
estoque_proj       = estoques + transito
dos_ideal          = tiers_doi[store_tier]           # 30 / 30 / 21 dias

estoque_ideal = ceil(dos_ideal * velocidade_vendas)   # ou 2, se velocidade e totalmente desconhecida
necessidade   = max(0, estoque_ideal - estoque_proj)

O piso de 2 unidades só se aplica quando nem a loja, nem o grupo de pares, nem o modelo têm dado suficiente. Um assert final garante que nenhuma loja fica sem número.

Etapa 5 · criar_excel_abastecimento

Rateio no centro de distribuição

A soma da necessidade de todas as lojas de um CD pode exceder o estoque real do CD.

proporcao = necessidade_loja / necessidade_total_cd

abastecimento = (
    floor(estoque_cd * proporcao)     # CD insuficiente -> rateia proporcionalmente
    if necessidade_total_cd > estoque_cd
    else necessidade_loja              # CD cobre tudo -> atende 100%
)

floor garante que a soma rateada nunca ultrapasse o estoque físico do CD.

Saída

O que sai do pipeline

Um Excel (base_necessidade_abastecimento.xlsx) com três abas, pronto para o time de trade marketing:

Mesmo resultado também em Parquet/CSV (data/05_model_input/) para análise programática.

Engenharia

O que sustenta a confiança no número

Limitações & próximos passos

O que ainda não está resolvido

Por design, este é um motor de decisão baseado em regras — não um modelo preditivo de caixa-preta. Escolha deliberada para manter o número auditável e defensável perante a operação.

Melhorias futuras

Para onde o projeto pode evoluir

O pipeline resolve o cálculo — os próximos passos são sobre alcance e usabilidade.

Front-end para uso mais amigável

Hoje o resultado é um Excel gerado via pipeline. Uma interface reduziria a barreira de uso para o time de trade marketing e permitiria ajustar parâmetros (tiers, pesos, cobertura) sem tocar em YAML.

Fluxo para os demais clientes

Reaproveitar a arquitetura — catálogo, cascata de fallback, fórmula de necessidade — para outros clientes/varejistas além da Claro, isolando apenas o que é específico de cada integração (formato de dado, cadastro de produto, regras de cluster).

A separação entre regra de negócio (parâmetros) e lógica (nós Kedro) já foi pensada para isso: generalizar o pipeline é, em grande parte, um trabalho de configuração — não de reescrita.

Obrigado

Guilherme Fidalgo
Programa Avançado em Data Science e Decisão · Insper
guilhermefvf@al.insper.edu.br
guilherme.fidalgo@oppo.com
↑ ↓ para navegar
01 / 24