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}**.")