Thiago Mateus Teles Amorim
Versão final e corrigida do dashboard
71f38f0
import streamlit as st
import pandas as pd
import os
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
# --- CONFIGURAÇÃO INICIAL DO DASHBOARD ---
st.set_page_config(layout="wide", page_title="Análise de Fornecedor | Queiroz Cartões")
# --- FUNÇÃO DE CARREGAMENTO DE DADOS (COM CACHE) ---
@st.cache_data
def carregar_dados():
script_dir = os.path.dirname(__file__)
caminho_do_arquivo = os.path.join(script_dir, 'dados.csv')
df = pd.read_csv(caminho_do_arquivo, header=None, encoding='latin-1', delimiter=';')
df.columns = [
'REMESSA', 'DATA_PEDIDO', 'DATA_RECEBIMENTO', 'DATA_DE_EXPEDICAO', 'RET-E', 'RET-R',
'TIPO_DE_PLASTICO', 'CHIP', 'QTD_PLASTICO', 'DISTRIBUIDORA', 'TIPO_DE_MAQUINA',
'RENOVACAO', 'FABRICA'
]
df = df.iloc[1:].reset_index(drop=True)
df['DATA_RECEBIMENTO'] = pd.to_datetime(df['DATA_RECEBIMENTO'], dayfirst=True, errors='coerce')
df['DATA_DE_EXPEDICAO'] = pd.to_datetime(df['DATA_DE_EXPEDICAO'], dayfirst=True, errors='coerce')
df.dropna(subset=['DATA_RECEBIMENTO', 'DATA_DE_EXPEDICAO'], inplace=True)
df['QTD_PLASTICO'] = pd.to_numeric(df['QTD_PLASTICO'], errors='coerce').fillna(0)
df = df[df['QTD_PLASTICO'] > 0].copy()
df['dias_para_expedir'] = (df['DATA_DE_EXPEDICAO'] - df['DATA_RECEBIMENTO']).dt.days
df = df[df['dias_para_expedir'] >= 0].copy()
df['TIPO_DE_PLASTICO'] = df['TIPO_DE_PLASTICO'].str.strip().str.lower()
df['Mês'] = df['DATA_RECEBIMENTO'].dt.strftime('%Y-%m')
df['sla_status'] = df['dias_para_expedir'] <= 3
# REGRA DE CONSISTÊNCIA FINAL
remessas_atrasadas_ids = df[df['sla_status'] == False]['REMESSA'].unique()
df.loc[df['REMESSA'].isin(remessas_atrasadas_ids), 'sla_status'] = False
return df
# --- TÍTULO PRINCIPAL ---
st.title("Análise de Performance de Fornecedor: Queiroz Cartões")
try:
df = carregar_dados()
except Exception as e:
st.error(f"Ocorreu um erro inesperado ao carregar os dados: {e}")
st.stop()
# --- CRIAÇÃO DAS ABAS ---
tab_resumo, tab_performance, tab_consumo_custos = st.tabs([
"Resumo Gerencial",
"Performance do Fornecedor (SLA)",
"Análise de Consumo e Custos"
])
# --- CONTEÚDO DA ABA 1: RESUMO GERENCIAL ---
with tab_resumo:
st.header("Diagnóstico de Personalizaçõa e Expedição/Envio de Cartões")
st.markdown("---")
# Cálculos para os KPIs
total_remessas = df['REMESSA'].nunique()
remessas_no_prazo = df[df['sla_status'] == True]['REMESSA'].nunique()
total_remessas_atrasadas = total_remessas - remessas_no_prazo
sla_geral = (remessas_no_prazo / total_remessas) * 100 if total_remessas > 0 else 0
meta_sla = 95.0
custos_map = {'platinum': 1.80, 'gold': 1.90, 'black': 2.20}
consumo_total = df.groupby('TIPO_DE_PLASTICO')['QTD_PLASTICO'].sum()
custo_total_personalizacao = (consumo_total * consumo_total.index.map(custos_map)).sum()
# --- ALTERAÇÃO 1: 4 KPIs NO RESUMO ---
col1, col2, col3, col4 = st.columns(4)
col1.metric(
label="SLA Geral de Expedição (Meta: 95%)",
value=f"{sla_geral:.2f}%",
delta=f"{(sla_geral - meta_sla):.2f}% vs Meta",
delta_color="normal"
)
col2.metric(
label="Total de Remessas 🚚",
value=f"{total_remessas}"
)
col3.metric(
label="Total de Remessas Atrasadas 📦",
value=f"{total_remessas_atrasadas}"
)
col4.metric(
label="Custo de Personalização Total 💳",
value=f"R$ {custo_total_personalizacao:,.2f}"
)
st.markdown("---")
st.subheader("Diagnóstico da Situação")
st.warning(
f"""
A análise da operação revela uma **falha crítica de performance** do fornecedor Queiroz Cartões.
De um total de **{total_remessas} remessas** analisadas, **{total_remessas_atrasadas} foram expedidas com atraso**, resultando em um SLA médio de apenas **{sla_geral:.2f}%**, muito abaixo da meta contratual de 95%.
A análise na aba 'Performance' demonstra que a falha é **crônica e sistêmica**, impactando diretamente a experiência do cooperado.
"""
)
st.subheader("Recomendação para Tomada de Decisão")
with st.expander("Clique aqui para ver o Plano de Ação Recomendado"):
st.info(
"""
**Recomenda-se o envio de um alerta ao atual fornecedor e prospecção de potenciais fornecedores substitutos para eventual quebra de contrato.**
**Plano de Ação Detalhado:**
* **Imediato (1 semana):** Notificação formal de quebra de SLA e convocação de reunião com o fornecedor.
* **Curto Prazo (1 mês):** Aplicação de penalidades contratuais e prospecção de fornecedores alternativos.
* **Médio Prazo (2 meses):** Reavaliação e, se a performance não atingir a meta, início do processo de rescisão contratual. Caso contrario, prosseguir com a execução contratual com o atual fornecedor
"""
)
# --- CONTEÚDO DA ABA 2: PERFORMANCE (SLA) ---
with tab_performance:
st.header("Análise Detalhada do Nível de Serviço (SLA)")
st.markdown("---")
remessas_atrasadas_df = df[df['sla_status'] == False]
tempo_medio_atraso = remessas_atrasadas_df['dias_para_expedir'].mean()
tempo_medio_geral = df['dias_para_expedir'].mean()
col1, col2, col3 = st.columns(3)
col1.metric("Total de Remessas no Prazo ✅", remessas_no_prazo)
col2.metric("Total de Remessas Atrasadas ❌", total_remessas_atrasadas)
col3.metric("Tempo Médio de Atraso 🗓️", f"{tempo_medio_atraso:.1f} dias")
st.markdown(f"O tempo médio geral de expedição (contando todas as remessas) é de **{tempo_medio_geral:.1f} dias**.")
st.markdown("---")
st.subheader("Performance Mensal do SLA vs. Meta (95%)")
# Cálculo do SLA Mensal consistente
sla_mensal_df = df.groupby('Mês')['REMESSA'].nunique().reset_index()
sla_mensal_df.rename(columns={'REMESSA': 'total_remessas'}, inplace=True)
remessas_no_prazo_mensal = df[df['sla_status'] == True].groupby('Mês')['REMESSA'].nunique().reset_index()
remessas_no_prazo_mensal.rename(columns={'REMESSA': 'remessas_no_prazo'}, inplace=True)
sla_mensal_final = pd.merge(sla_mensal_df, remessas_no_prazo_mensal, on='Mês', how='left').fillna(0)
sla_mensal_final['SLA'] = (sla_mensal_final['remessas_no_prazo'] / sla_mensal_final['total_remessas']) * 100
# Gráfico
fig, ax = plt.subplots(figsize=(12, 6))
sns.set_style("whitegrid")
ax.plot(sla_mensal_final['Mês'], sla_mensal_final['SLA'], marker='o', label='SLA Mensal', color='royalblue', linewidth=2.5)
ax.axhline(y=meta_sla, color='red', linestyle='--', label=f'Meta de {meta_sla}%')
ax.set_title('Performance Mensal do SLA vs. Meta', fontsize=16)
ax.set_ylabel('SLA (%)', fontsize=12)
ax.set_xlabel('Mês', fontsize=12)
ax.set_ylim(0, 105)
ax.legend()
st.pyplot(fig)
# --- ALTERAÇÃO 3: ADICIONANDO OBSERVAÇÃO ESTRATÉGICA ---
st.info(
"""
**💡 Observação Importante:** É notável que o melhor desempenho de SLA do fornecedor ocorreu em **Junho**,
o mesmo mês que, segundo a aba 'Consumo e Custos', teve o **menor volume de demanda**.
Isso pode indicar que o fornecedor tem capacidade para atingir a meta, mas apenas sob baixa carga de trabalho,
sugerindo um problema de escalabilidade em sua operação.
"""
)
st.markdown("---")
# --- ALTERAÇÃO 2: ADICIONANDO TABELA DE DADOS DO GRÁFICO ---
st.subheader("Dados do Gráfico de SLA Mensal")
sla_tabela_display = sla_mensal_final[['Mês', 'SLA', 'total_remessas', 'remessas_no_prazo']]
sla_tabela_display['SLA'] = sla_tabela_display['SLA'].map('{:.2f}%'.format)
st.dataframe(sla_tabela_display)
# --- CONTEÚDO DA ABA 3: CONSUMO E CUSTOS ---
with tab_consumo_custos:
# (O conteúdo desta aba permanece o mesmo)
st.header("Análise Detalhada de Consumo e Custos")
st.markdown("---")
st.subheader("Custos de Personalização")
col1, col2 = st.columns(2)
col1.metric(label="Custo Total de Personalização no Período", value=f"R$ {custo_total_personalizacao:,.2f}")
with col2:
custo_por_plastico = (consumo_total * consumo_total.index.map(custos_map))
st.dataframe(custo_por_plastico.round(2).rename("Custo por Produto"))
st.markdown("---")
st.subheader("Consumo de Plástico")
col1, col2 = st.columns(2)
with col1:
st.markdown("##### Consumo Mensal Total (Unidades)")
volume_mensal = df.groupby('Mês')['QTD_PLASTICO'].sum()
st.bar_chart(volume_mensal)
with col2:
st.markdown("##### Consumo Total por Produto (Unidades)")
st.dataframe(consumo_total.rename("Total Unidades"))
st.markdown("---")
st.subheader("Análise de Custo de Compra (Projeção)")
num_meses_analisados = df['Mês'].nunique()
consumo_medio_mensal = consumo_total / num_meses_analisados
def calcular_custo_unitario_compra(v):
if v <= 10000: return 5.85
elif v <= 20000: return 5.50
elif v <= 35000: return 5.10
elif v <= 45000: return 4.80
elif v <= 55000: return 4.50
else: return 4.10
volume_total_6_meses = (consumo_medio_mensal * 6).sum()
custo_total_6_meses = volume_total_6_meses * calcular_custo_unitario_compra(volume_total_6_meses)
volume_total_12_meses = (consumo_medio_mensal * 12).sum()
custo_total_12_meses = volume_total_12_meses * calcular_custo_unitario_compra(volume_total_12_meses)
col_cenario1, col_cenario2 = st.columns(2)
with col_cenario1:
st.markdown("##### Cenário de Compra para 6 Meses")
st.metric("Custo Total", f"R$ {custo_total_6_meses:,.2f}")
st.markdown(f"Volume: {volume_total_6_meses:,.0f} un. | Custo/Un: R$ {calcular_custo_unitario_compra(volume_total_6_meses):.2f}")
with col_cenario2:
st.markdown("##### Cenário de Compra para 12 Meses")
st.metric("Custo Total", f"R$ {custo_total_12_meses:,.2f}")
st.markdown(f"Volume: {volume_total_12_meses:,.0f} un. | Custo/Un: R$ {calcular_custo_unitario_compra(volume_total_12_meses):.2f}")
st.markdown("")
economia_anual = (custo_total_6_meses * 2) - custo_total_12_meses
if economia_anual > 0:
st.success(f"💡 **Recomendação:** Optar pelo cenário de 12 meses gera uma **economia anual de R$ {economia_anual:,.2f}**.")