Projetos
Projeto Completo · End-to-End · Desenvolvimento

LH Nautical — Como foi feito (explicado fácil)

Imagina que a LH Nautical era uma oficina mecânica desorganizada: ferramentas espalhadas, peças sem etiqueta, cada caixa numa língua diferente. Antes de "ler" o que tava acontecendo no negócio, eu precisava arrumar a oficina. Esse projeto é a história desse "antes e depois" — só que com dados.

Nível Intermediário a avançado Stack Python · Pandas · SQL · Power BI Tipo Projeto end-to-end
Visão geral

O que é isso, em uma frase?

É um projeto que pega 4 bases brutas e bagunçadas de uma rede de varejo náutico (loja física + e-commerce), arruma tudo num pipeline em 3 camadas, e em cima disso entrega: análise de vendas, segmentação de clientes, previsão de demanda e recomendação de produtos. R$ 2,61 bilhões de receita passaram por essa "esteira".

O nome técnico do método é arquitetura medallion — três camadas, como medalhas: bronze (dado cru), prata (dado limpo), ouro (dado pronto pra análise). Quem usa Databricks ou Snowflake conhece esse padrão de cor.

Por que importa? Porque qualquer análise feita em cima de dado sujo é mentira bonita. A arquitetura medallion é a forma "industrial" de garantir que toda análise parte de fonte confiável — e que, se a fonte mudar, dá pra rastrear o que foi afetado.

Passo a passo

Bronze · Prata · Ouro

1 · Bronze (dado cru, do jeito que chegou). Recebi 4 bases: vendas_2023_2024.csv (9.895 transações), produtos_raw.csv (157 linhas), clientes_crm.json (49 clientes) e custos_importacao.json (histórico de preços em USD). Aqui não toco em nada — a pasta data/bronze/ guarda os arquivos exatamente como chegaram. Se der ruim mais tarde, eu volto aqui. O módulo de ingestão (src/ingestion/ingest.py) só lê e loga o que entrou:

def load_csv(file_name: str, source_dir: Path = BRONZE_DIR, **kwargs) -> Optional[pd.DataFrame]:
    """Carrega um arquivo CSV do diretório de origem."""
    path = source_dir / file_name
    if not path.exists():
        logger.error("Arquivo não encontrado: %s", path)
        return None
    df = pd.read_csv(path, encoding="utf-8", **kwargs)
    logger.info("[Bronze] %s → %d linhas × %d colunas", file_name, *df.shape)
    return df

Por que logar "linhas × colunas"? É o recibo da entrega: se amanhã o arquivo chegar com menos linhas, o log denuncia na hora.

2 · Prata (dado limpo e padronizado). Aqui eu corrijo os 7 problemas que o cliente nem sabia que tinha: datas em 2 formatos (o e-commerce grava 2023-01-15, a loja física 15-01-2023), preço como texto ("R$ 33.122,52"), 38 grafias diferentes para só 3 categorias, 7 produtos duplicados, localização em 3 formatos, 61% dos e-mails inválidos e o histórico de custo em USD aninhado dentro do JSON. Cada correção é uma função pequena e testável de src/processing/transform.py. Duas delas, na íntegra:

def _parse_sale_date(val) -> pd.Timestamp:
    """Suporta YYYY-MM-DD (e-commerce) e DD-MM-YYYY (loja física).
    A distinção é feita pelo comprimento do primeiro segmento."""
    if pd.isna(val):
        return pd.NaT
    parts = str(val).strip().split("-")
    if len(parts) != 3:
        return pd.NaT
    try:
        if len(parts[0]) == 4:                # YYYY-MM-DD
            return pd.Timestamp(str(val).strip())
        return pd.to_datetime(str(val).strip(), format="%d-%m-%Y")
    except Exception:
        return pd.NaT

def _clean_price(val) -> float:
    """'R$ 33.122,52' → 33122.52  ·  'R$ 1890.00' → 1890.0"""
    if pd.isna(val):
        return np.nan
    s = str(val).replace("R$", "").strip()
    if "," in s and "." in s:                 # formato pt-BR: 1.890,00
        s = s.replace(".", "").replace(",", ".")
    elif "," in s:
        s = s.replace(",", ".")
    try:
        return float(s.replace(" ", ""))
    except ValueError:
        return np.nan

Repara no truque da data: se o primeiro pedaço tem 4 dígitos, é ano (formato do e-commerce); senão, é dia (formato da loja). Simples e à prova dos dois sistemas. E os e-mails inválidos? Não apaguei nenhum cliente — criei uma coluna email_valido (True/False), porque jogar fora 61% da base seria pior que o problema.

O resultado: tabelas no padrão "1 linha = 1 fato real". Daqui pra frente, toda análise vai dar o mesmo número.

3 · Ouro (dado pronto pra negócio). A camada ouro organiza tudo num star schema (modelo estrela de Kimball): a fato_vendas (9.895 linhas — uma por transação, só métricas e chaves) cercada pelas dimensões dim_clientes (49 registros, com a flag email_valido e cidade/estado já parseados) e dim_produtos (150 SKUs, com custo de importação e margem unitária calculada). Cada dashboard puxa direto daqui — rápido, e sempre com o mesmo número.

# dim_produtos: catálogo + custo em BRL + margem por SKU
dim_produtos = (
    produtos[["id_produto", "nome_produto", "categoria", "preco_venda"]]
    .merge(custos[["id_produto", "custo_unitario_brl"]], on="id_produto", how="left")
)
dim_produtos["margem_unitaria_pct"] = (
    (dim_produtos["preco_venda"] - dim_produtos["custo_unitario_brl"])
    / dim_produtos["preco_venda"] * 100
).round(2)

É essa coluna margem_unitaria_pct que revelou o achado mais valioso do projeto: 30 produtos vendidos abaixo do custo.

4 · Em cima do ouro, 6 frentes de entrega:

  • EDA — exploração que mapeou os 7 problemas de qualidade antes de qualquer análise
  • Tratamento — o pipeline bronze → prata → ouro descrito acima (roda inteiro em ~2s via python main.py)
  • Vendas — KPIs, evolução mensal, alerta de produtos vendidos abaixo do custo (R$ 22 mi descobertos)
  • Clientes — Pareto (Top 5 = 12,7% · Top 10 = 24,4%), geografia, identificação dos 49 clientes B2B
  • Previsão — Média Móvel + Regressão Linear, com nota explícita sobre limitações (R² baixo)
  • Recomendação — popularidade, produtos relacionados e market basket simplificada: pares de produtos comprados pelos mesmos clientes, rankeados por suporte

Quem consome o ouro: os notebooks (um por frente), o Power BI e o web app Streamlit de 6 páginas hospedado no Render.

Resultado

A "esteira" do dado

O que dá pra levar daqui

Em uma linha: o que esse projeto ensina

Que dado bom não nasce — se constrói por etapas. Pular a prata pra ir direto pro dashboard é o erro mais comum (e o mais caro): toda divergência futura vai obrigar a refazer tudo do zero. Medallion existe pra isso ser reproduzível.

Coisas técnicas que apareceram aqui: arquitetura medallion (bronze/prata/ouro), Pandas para ETL, Parquet (formato colunar), normalização de tipos, deduplicação, regras de qualidade, modelagem dimensional (fato/dimensões), LTV, Pareto, Power BI sobre camada ouro.