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