Thiago Mateus Teles Amorim commited on
Commit
71f38f0
·
1 Parent(s): 69aebfd

Versão final e corrigida do dashboard

Browse files
Files changed (2) hide show
  1. app.py +210 -13
  2. verificar_colunas.py +46 -0
app.py CHANGED
@@ -1,37 +1,234 @@
1
  import streamlit as st
2
  import pandas as pd
 
3
  import matplotlib.pyplot as plt
4
  import seaborn as sns
 
5
 
6
  # --- CONFIGURAÇÃO INICIAL DO DASHBOARD ---
7
  st.set_page_config(layout="wide", page_title="Análise de Fornecedor | Queiroz Cartões")
8
 
9
  # --- FUNÇÃO DE CARREGAMENTO DE DADOS (COM CACHE) ---
10
  @st.cache_data
11
- def carregar_dados(file_name='dados.csv'): # MUDANÇA 1: Nome do arquivo
12
- # MUDANÇA 2: Usando pd.read_csv, que é mais leve
13
- df = pd.read_csv(file_name, header=None)
14
- # Em CSVs, geralmente não precisamos pular linhas se salvamos a planilha correta.
15
- # O header=None continua importante pois o arquivo CSV não terá nomes de coluna.
16
 
17
  df.columns = [
18
  'REMESSA', 'DATA_PEDIDO', 'DATA_RECEBIMENTO', 'DATA_DE_EXPEDICAO', 'RET-E', 'RET-R',
19
  'TIPO_DE_PLASTICO', 'CHIP', 'QTD_PLASTICO', 'DISTRIBUIDORA', 'TIPO_DE_MAQUINA',
20
  'RENOVACAO', 'FABRICA'
21
  ]
22
- # Limpeza e preparação dos dados
23
- df['DATA_RECEBIMENTO'] = pd.to_datetime(df['DATA_RECEBIMENTO'], format='mixed')
24
- df['DATA_DE_EXPEDICAO'] = pd.to_datetime(df['DATA_DE_EXPEDICAO'], format='mixed')
 
 
 
25
  df['QTD_PLASTICO'] = pd.to_numeric(df['QTD_PLASTICO'], errors='coerce').fillna(0)
26
  df = df[df['QTD_PLASTICO'] > 0].copy()
 
 
27
  df['TIPO_DE_PLASTICO'] = df['TIPO_DE_PLASTICO'].str.strip().str.lower()
28
  df['Mês'] = df['DATA_RECEBIMENTO'].dt.strftime('%Y-%m')
29
- df['dias_para_expedir'] = (df['DATA_DE_EXPEDICAO'] - df['DATA_RECEBIMENTO']).dt.days
30
  df['sla_status'] = df['dias_para_expedir'] <= 3
 
 
 
 
 
31
  return df
32
 
33
- # O restante do código do dashboard continua exatamente o mesmo...
34
- # (O código foi omitido aqui por brevidade, mas use o seu código completo da versão anterior)
35
- # --- Início do Dashboard ---
36
  st.title("Análise de Performance de Fornecedor: Queiroz Cartões")
37
- # ... cole o resto do seu código do dashboard_melhorado.py aqui
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import streamlit as st
2
  import pandas as pd
3
+ import os
4
  import matplotlib.pyplot as plt
5
  import seaborn as sns
6
+ import numpy as np
7
 
8
  # --- CONFIGURAÇÃO INICIAL DO DASHBOARD ---
9
  st.set_page_config(layout="wide", page_title="Análise de Fornecedor | Queiroz Cartões")
10
 
11
  # --- FUNÇÃO DE CARREGAMENTO DE DADOS (COM CACHE) ---
12
  @st.cache_data
13
+ def carregar_dados():
14
+ script_dir = os.path.dirname(__file__)
15
+ caminho_do_arquivo = os.path.join(script_dir, 'dados.csv')
16
+
17
+ df = pd.read_csv(caminho_do_arquivo, header=None, encoding='latin-1', delimiter=';')
18
 
19
  df.columns = [
20
  'REMESSA', 'DATA_PEDIDO', 'DATA_RECEBIMENTO', 'DATA_DE_EXPEDICAO', 'RET-E', 'RET-R',
21
  'TIPO_DE_PLASTICO', 'CHIP', 'QTD_PLASTICO', 'DISTRIBUIDORA', 'TIPO_DE_MAQUINA',
22
  'RENOVACAO', 'FABRICA'
23
  ]
24
+
25
+ df = df.iloc[1:].reset_index(drop=True)
26
+
27
+ df['DATA_RECEBIMENTO'] = pd.to_datetime(df['DATA_RECEBIMENTO'], dayfirst=True, errors='coerce')
28
+ df['DATA_DE_EXPEDICAO'] = pd.to_datetime(df['DATA_DE_EXPEDICAO'], dayfirst=True, errors='coerce')
29
+ df.dropna(subset=['DATA_RECEBIMENTO', 'DATA_DE_EXPEDICAO'], inplace=True)
30
  df['QTD_PLASTICO'] = pd.to_numeric(df['QTD_PLASTICO'], errors='coerce').fillna(0)
31
  df = df[df['QTD_PLASTICO'] > 0].copy()
32
+ df['dias_para_expedir'] = (df['DATA_DE_EXPEDICAO'] - df['DATA_RECEBIMENTO']).dt.days
33
+ df = df[df['dias_para_expedir'] >= 0].copy()
34
  df['TIPO_DE_PLASTICO'] = df['TIPO_DE_PLASTICO'].str.strip().str.lower()
35
  df['Mês'] = df['DATA_RECEBIMENTO'].dt.strftime('%Y-%m')
 
36
  df['sla_status'] = df['dias_para_expedir'] <= 3
37
+
38
+ # REGRA DE CONSISTÊNCIA FINAL
39
+ remessas_atrasadas_ids = df[df['sla_status'] == False]['REMESSA'].unique()
40
+ df.loc[df['REMESSA'].isin(remessas_atrasadas_ids), 'sla_status'] = False
41
+
42
  return df
43
 
44
+ # --- TÍTULO PRINCIPAL ---
 
 
45
  st.title("Análise de Performance de Fornecedor: Queiroz Cartões")
46
+
47
+ try:
48
+ df = carregar_dados()
49
+ except Exception as e:
50
+ st.error(f"Ocorreu um erro inesperado ao carregar os dados: {e}")
51
+ st.stop()
52
+
53
+ # --- CRIAÇÃO DAS ABAS ---
54
+ tab_resumo, tab_performance, tab_consumo_custos = st.tabs([
55
+ "Resumo Gerencial",
56
+ "Performance do Fornecedor (SLA)",
57
+ "Análise de Consumo e Custos"
58
+ ])
59
+
60
+ # --- CONTEÚDO DA ABA 1: RESUMO GERENCIAL ---
61
+ with tab_resumo:
62
+ st.header("Diagnóstico de Personalizaçõa e Expedição/Envio de Cartões")
63
+ st.markdown("---")
64
+
65
+ # Cálculos para os KPIs
66
+ total_remessas = df['REMESSA'].nunique()
67
+ remessas_no_prazo = df[df['sla_status'] == True]['REMESSA'].nunique()
68
+ total_remessas_atrasadas = total_remessas - remessas_no_prazo
69
+ sla_geral = (remessas_no_prazo / total_remessas) * 100 if total_remessas > 0 else 0
70
+ meta_sla = 95.0
71
+ custos_map = {'platinum': 1.80, 'gold': 1.90, 'black': 2.20}
72
+ consumo_total = df.groupby('TIPO_DE_PLASTICO')['QTD_PLASTICO'].sum()
73
+ custo_total_personalizacao = (consumo_total * consumo_total.index.map(custos_map)).sum()
74
+
75
+ # --- ALTERAÇÃO 1: 4 KPIs NO RESUMO ---
76
+ col1, col2, col3, col4 = st.columns(4)
77
+
78
+ col1.metric(
79
+ label="SLA Geral de Expedição (Meta: 95%)",
80
+ value=f"{sla_geral:.2f}%",
81
+ delta=f"{(sla_geral - meta_sla):.2f}% vs Meta",
82
+ delta_color="normal"
83
+ )
84
+ col2.metric(
85
+ label="Total de Remessas 🚚",
86
+ value=f"{total_remessas}"
87
+ )
88
+ col3.metric(
89
+ label="Total de Remessas Atrasadas 📦",
90
+ value=f"{total_remessas_atrasadas}"
91
+ )
92
+ col4.metric(
93
+ label="Custo de Personalização Total 💳",
94
+ value=f"R$ {custo_total_personalizacao:,.2f}"
95
+ )
96
+
97
+ st.markdown("---")
98
+
99
+ st.subheader("Diagnóstico da Situação")
100
+ st.warning(
101
+ f"""
102
+ A análise da operação revela uma **falha crítica de performance** do fornecedor Queiroz Cartões.
103
+ 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%.
104
+ A análise na aba 'Performance' demonstra que a falha é **crônica e sistêmica**, impactando diretamente a experiência do cooperado.
105
+ """
106
+ )
107
+
108
+ st.subheader("Recomendação para Tomada de Decisão")
109
+ with st.expander("Clique aqui para ver o Plano de Ação Recomendado"):
110
+ st.info(
111
+ """
112
+ **Recomenda-se o envio de um alerta ao atual fornecedor e prospecção de potenciais fornecedores substitutos para eventual quebra de contrato.**
113
+
114
+ **Plano de Ação Detalhado:**
115
+ * **Imediato (1 semana):** Notificação formal de quebra de SLA e convocação de reunião com o fornecedor.
116
+ * **Curto Prazo (1 mês):** Aplicação de penalidades contratuais e prospecção de fornecedores alternativos.
117
+ * **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
118
+ """
119
+ )
120
+
121
+ # --- CONTEÚDO DA ABA 2: PERFORMANCE (SLA) ---
122
+ with tab_performance:
123
+ st.header("Análise Detalhada do Nível de Serviço (SLA)")
124
+ st.markdown("---")
125
+
126
+ remessas_atrasadas_df = df[df['sla_status'] == False]
127
+ tempo_medio_atraso = remessas_atrasadas_df['dias_para_expedir'].mean()
128
+ tempo_medio_geral = df['dias_para_expedir'].mean()
129
+
130
+ col1, col2, col3 = st.columns(3)
131
+ col1.metric("Total de Remessas no Prazo ✅", remessas_no_prazo)
132
+ col2.metric("Total de Remessas Atrasadas ❌", total_remessas_atrasadas)
133
+ col3.metric("Tempo Médio de Atraso 🗓️", f"{tempo_medio_atraso:.1f} dias")
134
+ st.markdown(f"O tempo médio geral de expedição (contando todas as remessas) é de **{tempo_medio_geral:.1f} dias**.")
135
+ st.markdown("---")
136
+
137
+ st.subheader("Performance Mensal do SLA vs. Meta (95%)")
138
+
139
+ # Cálculo do SLA Mensal consistente
140
+ sla_mensal_df = df.groupby('Mês')['REMESSA'].nunique().reset_index()
141
+ sla_mensal_df.rename(columns={'REMESSA': 'total_remessas'}, inplace=True)
142
+ remessas_no_prazo_mensal = df[df['sla_status'] == True].groupby('Mês')['REMESSA'].nunique().reset_index()
143
+ remessas_no_prazo_mensal.rename(columns={'REMESSA': 'remessas_no_prazo'}, inplace=True)
144
+ sla_mensal_final = pd.merge(sla_mensal_df, remessas_no_prazo_mensal, on='Mês', how='left').fillna(0)
145
+ sla_mensal_final['SLA'] = (sla_mensal_final['remessas_no_prazo'] / sla_mensal_final['total_remessas']) * 100
146
+
147
+ # Gráfico
148
+ fig, ax = plt.subplots(figsize=(12, 6))
149
+ sns.set_style("whitegrid")
150
+ ax.plot(sla_mensal_final['Mês'], sla_mensal_final['SLA'], marker='o', label='SLA Mensal', color='royalblue', linewidth=2.5)
151
+ ax.axhline(y=meta_sla, color='red', linestyle='--', label=f'Meta de {meta_sla}%')
152
+ ax.set_title('Performance Mensal do SLA vs. Meta', fontsize=16)
153
+ ax.set_ylabel('SLA (%)', fontsize=12)
154
+ ax.set_xlabel('Mês', fontsize=12)
155
+ ax.set_ylim(0, 105)
156
+ ax.legend()
157
+ st.pyplot(fig)
158
+
159
+ # --- ALTERAÇÃO 3: ADICIONANDO OBSERVAÇÃO ESTRATÉGICA ---
160
+ st.info(
161
+ """
162
+ **💡 Observação Importante:** É notável que o melhor desempenho de SLA do fornecedor ocorreu em **Junho**,
163
+ o mesmo mês que, segundo a aba 'Consumo e Custos', teve o **menor volume de demanda**.
164
+ Isso pode indicar que o fornecedor tem capacidade para atingir a meta, mas apenas sob baixa carga de trabalho,
165
+ sugerindo um problema de escalabilidade em sua operação.
166
+ """
167
+ )
168
+ st.markdown("---")
169
+
170
+ # --- ALTERAÇÃO 2: ADICIONANDO TABELA DE DADOS DO GRÁFICO ---
171
+ st.subheader("Dados do Gráfico de SLA Mensal")
172
+ sla_tabela_display = sla_mensal_final[['Mês', 'SLA', 'total_remessas', 'remessas_no_prazo']]
173
+ sla_tabela_display['SLA'] = sla_tabela_display['SLA'].map('{:.2f}%'.format)
174
+ st.dataframe(sla_tabela_display)
175
+
176
+
177
+ # --- CONTEÚDO DA ABA 3: CONSUMO E CUSTOS ---
178
+ with tab_consumo_custos:
179
+ # (O conteúdo desta aba permanece o mesmo)
180
+ st.header("Análise Detalhada de Consumo e Custos")
181
+ st.markdown("---")
182
+
183
+ st.subheader("Custos de Personalização")
184
+ col1, col2 = st.columns(2)
185
+ col1.metric(label="Custo Total de Personalização no Período", value=f"R$ {custo_total_personalizacao:,.2f}")
186
+ with col2:
187
+ custo_por_plastico = (consumo_total * consumo_total.index.map(custos_map))
188
+ st.dataframe(custo_por_plastico.round(2).rename("Custo por Produto"))
189
+ st.markdown("---")
190
+
191
+ st.subheader("Consumo de Plástico")
192
+ col1, col2 = st.columns(2)
193
+ with col1:
194
+ st.markdown("##### Consumo Mensal Total (Unidades)")
195
+ volume_mensal = df.groupby('Mês')['QTD_PLASTICO'].sum()
196
+ st.bar_chart(volume_mensal)
197
+ with col2:
198
+ st.markdown("##### Consumo Total por Produto (Unidades)")
199
+ st.dataframe(consumo_total.rename("Total Unidades"))
200
+ st.markdown("---")
201
+
202
+ st.subheader("Análise de Custo de Compra (Projeção)")
203
+ num_meses_analisados = df['Mês'].nunique()
204
+ consumo_medio_mensal = consumo_total / num_meses_analisados
205
+
206
+ def calcular_custo_unitario_compra(v):
207
+ if v <= 10000: return 5.85
208
+ elif v <= 20000: return 5.50
209
+ elif v <= 35000: return 5.10
210
+ elif v <= 45000: return 4.80
211
+ elif v <= 55000: return 4.50
212
+ else: return 4.10
213
+
214
+ volume_total_6_meses = (consumo_medio_mensal * 6).sum()
215
+ custo_total_6_meses = volume_total_6_meses * calcular_custo_unitario_compra(volume_total_6_meses)
216
+
217
+ volume_total_12_meses = (consumo_medio_mensal * 12).sum()
218
+ custo_total_12_meses = volume_total_12_meses * calcular_custo_unitario_compra(volume_total_12_meses)
219
+
220
+ col_cenario1, col_cenario2 = st.columns(2)
221
+ with col_cenario1:
222
+ st.markdown("##### Cenário de Compra para 6 Meses")
223
+ st.metric("Custo Total", f"R$ {custo_total_6_meses:,.2f}")
224
+ st.markdown(f"Volume: {volume_total_6_meses:,.0f} un. | Custo/Un: R$ {calcular_custo_unitario_compra(volume_total_6_meses):.2f}")
225
+
226
+ with col_cenario2:
227
+ st.markdown("##### Cenário de Compra para 12 Meses")
228
+ st.metric("Custo Total", f"R$ {custo_total_12_meses:,.2f}")
229
+ st.markdown(f"Volume: {volume_total_12_meses:,.0f} un. | Custo/Un: R$ {calcular_custo_unitario_compra(volume_total_12_meses):.2f}")
230
+
231
+ st.markdown("")
232
+ economia_anual = (custo_total_6_meses * 2) - custo_total_12_meses
233
+ if economia_anual > 0:
234
+ st.success(f"💡 **Recomendação:** Optar pelo cenário de 12 meses gera uma **economia anual de R$ {economia_anual:,.2f}**.")
verificar_colunas.py ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pandas as pd
2
+ import os
3
+
4
+ def carregar_dados_final():
5
+ script_dir = os.path.dirname(__file__)
6
+ caminho_do_arquivo = os.path.join(script_dir, 'dados.csv')
7
+
8
+ df = pd.read_csv(caminho_do_arquivo, header=None, encoding='latin-1', delimiter=';')
9
+
10
+ df.columns = [
11
+ 'REMESSA', 'DATA_PEDIDO', 'DATA_RECEBIMENTO', 'DATA_DE_EXPEDICAO', 'RET-E', 'RET-R',
12
+ 'TIPO_DE_PLASTICO', 'CHIP', 'QTD_PLASTICO', 'DISTRIBUIDORA', 'TIPO_DE_MAQUINA',
13
+ 'RENOVACAO', 'FABRICA'
14
+ ]
15
+
16
+ df = df.iloc[1:].reset_index(drop=True)
17
+
18
+ df['DATA_RECEBIMENTO'] = pd.to_datetime(df['DATA_RECEBIMENTO'], dayfirst=True, errors='coerce')
19
+ df['DATA_DE_EXPEDICAO'] = pd.to_datetime(df['DATA_DE_EXPEDICAO'], dayfirst=True, errors='coerce')
20
+ df.dropna(subset=['DATA_RECEBIMENTO', 'DATA_DE_EXPEDICAO'], inplace=True)
21
+ df['QTD_PLASTICO'] = pd.to_numeric(df['QTD_PLASTICO'], errors='coerce').fillna(0)
22
+ df = df[df['QTD_PLASTICO'] > 0].copy()
23
+ df['dias_para_expedir'] = (df['DATA_DE_EXPEDICAO'] - df['DATA_RECEBIMENTO']).dt.days
24
+ df = df[df['dias_para_expedir'] >= 0].copy()
25
+ df['sla_status'] = df['dias_para_expedir'] <= 3
26
+
27
+ remessas_atrasadas_ids = df[df['sla_status'] == False]['REMESSA'].unique()
28
+ df.loc[df['REMESSA'].isin(remessas_atrasadas_ids), 'sla_status'] = False
29
+
30
+ return df
31
+
32
+ try:
33
+ df_final = carregar_dados_final()
34
+
35
+ total_remessas = df_final['REMESSA'].nunique()
36
+ remessas_no_prazo = df_final[df_final['sla_status'] == True]['REMESSA'].nunique()
37
+ remessas_atrasadas = df_final[df_final['sla_status'] == False]['REMESSA'].nunique()
38
+
39
+ print("\n--- Números Finais e Corretos da Análise ---")
40
+ print(f"Total de Remessas Únicas Relevantes: {total_remessas}")
41
+ print(f"Total de Remessas No Prazo: {remessas_no_prazo}")
42
+ print(f"Total de Remessas Atrasadas: {remessas_atrasadas}")
43
+ print("---------------------------------------------")
44
+
45
+ except Exception as e:
46
+ print(f"Ocorreu um erro: {e}")