Arthur-Attili commited on
Commit
1a003ae
·
verified ·
1 Parent(s): 6a1ba0f

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +118 -65
app.py CHANGED
@@ -3,18 +3,100 @@ import requests
3
  import io
4
  import itertools
5
  from collections import defaultdict
6
- import gradio as gr # Importação principal para Gradio
7
- import os # Para variáveis de ambiente, se necessário
 
8
 
9
  # 🔧 CONFIGURAÇÕES GLOBAIS
10
- # O ID da planilha para Consórcio de Automóveis
11
- # Certifique-se de que este FILE_ID esteja acessível publicamente ou para "Qualquer pessoa com o link"
12
  FILE_ID = "1EoH8V3Fyls0mNa95VcBN6ZL1B552ty3X"
13
  URL = f"https://drive.google.com/uc?export=download&id={FILE_ID}"
14
 
15
  # Variável global para armazenar o DataFrame
16
  df = None
17
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
 
19
  def carregar_planilha():
20
  """Carrega e prepara a planilha para análise de consórcios de automóveis."""
@@ -29,10 +111,10 @@ def carregar_planilha():
29
  # Padroniza nomes das colunas
30
  df.columns = df.columns.str.strip().str.lower().str.replace(" ", "_")
31
 
32
- # Padroniza os nomes das administradoras
33
  if 'administradora' in df.columns:
34
- df['administradora'] = df['administradora'].astype(str).str.strip().str.lower()
35
-
36
 
37
  # Converte colunas numéricas
38
  numeric_cols = ['valor_credito', 'valor_entrada', 'valor_parcela', 'saldo_devedor', 'qtd_parcelas']
@@ -47,11 +129,14 @@ def carregar_planilha():
47
  # Remove duplicatas
48
  df.drop_duplicates(inplace=True)
49
  print("✅ Planilha carregada com sucesso!")
 
 
 
 
50
  return True
51
 
52
  except Exception as e:
53
  print(f"❌ Erro ao carregar planilha: {str(e)}")
54
- # Retorne uma mensagem de erro mais útil para o usuário
55
  return False
56
 
57
  def agrupar_por_administradora(dataframe):
@@ -73,7 +158,7 @@ def buscar_combinacoes(credito_desejado, entrada_desejada, parcela_desejada, adm
73
  if administradoras:
74
  if isinstance(administradoras, tuple):
75
  administradoras = list(administradoras)
76
- administradoras = [a.strip().lower() for a in administradoras]
77
  df_filtrado = df_filtrado[df_filtrado['administradora'].isin(administradoras)]
78
 
79
  if df_filtrado.empty:
@@ -86,13 +171,13 @@ def buscar_combinacoes(credito_desejado, entrada_desejada, parcela_desejada, adm
86
 
87
  grupos = agrupar_por_administradora(df_filtrado)
88
  resultados = []
89
- MAX_COMBINACOES_POR_ADM = 50 # Limite para evitar processamento excessivo
90
 
91
  for adm, cotas in grupos.items():
92
- cotas = cotas[:25] # Limita a 25 cotas por administradora para evitar combinações explosivas
93
  combinacoes_validas = 0
94
 
95
- for r in range(1, min(6, len(cotas)) + 1): # Agora até 5 cotas
96
  for combinacao in itertools.combinations(cotas, r):
97
  if combinacoes_validas >= MAX_COMBINACOES_POR_ADM:
98
  break
@@ -116,10 +201,7 @@ def buscar_combinacoes(credito_desejado, entrada_desejada, parcela_desejada, adm
116
  if not resultados:
117
  return "Nenhuma combinação adequada encontrada para automóveis com os parâmetros fornecidos."
118
 
119
- # Ordena por diferença (melhores combinações primeiro)
120
  resultados.sort(key=lambda x: x[2])
121
-
122
- # Limita o número total de resultados para evitar sobrecarga
123
  resultados = resultados[:250]
124
 
125
  return formatar_resultados(resultados)
@@ -131,60 +213,36 @@ def formatar_resultados(resultados):
131
  """Formata os resultados para exibição."""
132
  resposta = ""
133
 
134
- # 1. Agrupar os resultados por administradora, mantendo a diferença para ordenação interna
135
- # grouped_by_adm = {'Administradora X': [(combinacao_A, 100), (combinacao_C, 180), ...],
136
- # 'Administradora Y': [(combinacao_B, 150), ...]}
137
  grouped_by_adm = defaultdict(list)
138
  for adm, combinacao, diferenca in resultados:
139
  grouped_by_adm[adm].append((combinacao, diferenca))
140
 
141
- # 2. Ordenar as combinações dentro de cada administradora pela diferença (do menor para o maior)
142
  for adm in grouped_by_adm:
143
- grouped_by_adm[adm].sort(key=lambda x: x[1]) # x[1] é a diferença
144
 
145
  final_resultados_diversificados = []
146
-
147
- # Dicionário para acompanhar o índice da próxima combinação a ser pega de cada administradora
148
- # Ex: {'Itau': 0, 'Bradesco': 0, 'Caixa': 0}
149
  adm_indices = {adm: 0 for adm in grouped_by_adm.keys()}
150
-
151
- # Lista de administradoras ativas (que ainda têm combinações disponíveis)
152
  active_adms = list(grouped_by_adm.keys())
 
153
 
154
- current_adm_idx = 0 # Índice para ciclar entre as administradoras ativas
155
-
156
- # Loop para selecionar até 15 combinações, garantindo diversidade
157
  while len(final_resultados_diversificados) < 15 and active_adms:
158
- # Pega a administradora da vez para selecionar uma combinação
159
  adm_da_vez = active_adms[current_adm_idx]
160
-
161
- # Pega o índice da próxima combinação para essa administradora
162
  idx_na_adm = adm_indices[adm_da_vez]
163
 
164
- # Verifica se essa administradora ainda tem combinações disponíveis
165
  if idx_na_adm < len(grouped_by_adm[adm_da_vez]):
166
- # Pega a combinação (ignorando a diferença, que já foi usada para ordenar internamente)
167
  combinacao_data, _ = grouped_by_adm[adm_da_vez][idx_na_adm]
168
-
169
- # Adiciona à lista de resultados final (com a administradora e a combinação)
170
  final_resultados_diversificados.append((adm_da_vez, combinacao_data))
171
-
172
- # Incrementa o índice para a próxima vez que pegarmos dessa administradora
173
  adm_indices[adm_da_vez] += 1
174
  else:
175
- # Se essa administradora não tem mais combinações, remove ela da lista de ativas
176
  active_adms.pop(current_adm_idx)
177
- # Ajusta o current_adm_idx se a lista diminuiu e estamos no limite
178
  if current_adm_idx >= len(active_adms) and active_adms:
179
- current_adm_idx = 0 # Volta para o início da lista se removemos o último
180
 
181
- # Move para a próxima administradora no ciclo (garantindo que não estouramos o índice da lista active_adms)
182
- if active_adms: # Só avança se ainda houver administradoras ativas
183
  current_adm_idx = (current_adm_idx + 1) % len(active_adms)
184
  else:
185
- break # Não há mais administradoras ativas, sai do loop
186
 
187
- # Agora, itera sobre os resultados diversificados para formatar a resposta
188
  for idx, (adm, combinacao) in enumerate(final_resultados_diversificados, 1):
189
  codigos = [str(c.get('codigo', 'N/A')) for c in combinacao]
190
  total_credito = sum(c['valor_credito'] for c in combinacao)
@@ -200,14 +258,12 @@ def formatar_resultados(resultados):
200
  taxa_percentual = (taxa_total / credito_real) * 100 if credito_real > 0 else 0
201
  taxa_mensal = taxa_percentual / maior_prazo if maior_prazo > 0 else 0
202
 
203
- #comissao = total_entrada * (8.5 / 108.5)
204
-
205
  resposta += f"""
206
  --- COMBINAÇÃO {idx} ---
207
- �� Administradora: {adm}
208
  🔢 Códigos: {', '.join(codigos)}
209
  💰 Crédito Total: {formatar_moeda(total_credito)}
210
- �� Entrada (+comissão): {formatar_moeda(total_entrada)}
211
  📉 Saldo Devedor: {formatar_moeda(total_saldo)}
212
  💲 Parcela: {formatar_moeda(total_parcelas)}
213
  ⏳ Prazo: {maior_prazo} meses
@@ -220,24 +276,25 @@ def formatar_resultados(resultados):
220
  """
221
  return resposta
222
 
223
- # --- INICIALIZAÇÃO PARA GRADIO ---
224
- # Carrega a planilha UMA VEZ no início do aplicativo
225
- # Se der erro, a função de busca tentará carregar novamente.
226
- if not carregar_planilha():
227
- print("⚠️ Aviso: Planilha não carregada na inicialização. Tentará carregar na primeira busca.")
228
-
229
- # Calcula a entrada adicionando 8,5% ao valor final da entrada.
230
  def ajustar_e_buscar(credito_desejado, entrada_desejada, parcela_desejada, administradoras):
231
  entrada_calculada = credito_desejado * 0.085 + entrada_desejada
232
  return buscar_combinacoes(credito_desejado, entrada_calculada, parcela_desejada, administradoras)
233
 
234
- # Prepara as opções para o seletor de administradoras do Gradio
235
  def get_administradoras_options():
236
- if df is not None:
237
- adm_list = df[df['tipo_do_bem'].str.lower() == 'auto']['administradora'].unique()
238
- return sorted(adm_list.tolist()) # Converter para lista, pois .unique() pode retornar um numpy array
 
 
 
 
 
239
  return []
240
 
 
 
 
 
241
  # --- Interface Gradio ---
242
  with gr.Blocks(title="Sistema de Busca de Consórcio de Automóveis") as demo:
243
  gr.HTML("""
@@ -252,7 +309,6 @@ with gr.Blocks(title="Sistema de Busca de Consórcio de Automóveis") as demo:
252
  color: black !important;
253
  }
254
 
255
- /* Inputs Numéricos: Crédito, Entrada, Parcela */
256
  input[type="number"] {
257
  background-color: white !important;
258
  border: 2px solid #000 !important;
@@ -262,7 +318,6 @@ with gr.Blocks(title="Sistema de Busca de Consórcio de Automóveis") as demo:
262
  font-weight: 500;
263
  }
264
 
265
- /* Inputs de texto e áreas de texto (como a saída e código) */
266
  input[type="text"], textarea {
267
  background-color: white !important;
268
  border: 2px solid black !important;
@@ -320,7 +375,7 @@ with gr.Blocks(title="Sistema de Busca de Consórcio de Automóveis") as demo:
320
  administradoras_multiselect = gr.CheckboxGroup(
321
  label="Filtrar por Administradoras",
322
  choices=administradoras_options,
323
- value=administradoras_options, # Seleciona todas por padrão
324
  interactive=True
325
  )
326
 
@@ -328,16 +383,14 @@ with gr.Blocks(title="Sistema de Busca de Consórcio de Automóveis") as demo:
328
 
329
  output_text = gr.Textbox(label="Resultados da Busca", lines=20, max_lines=40, interactive=False, placeholder="Os resultados aparecerão aqui...")
330
 
331
- # Conecta o botão à função de busca
332
  submit_btn.click(
333
  fn=ajustar_e_buscar,
334
  inputs=[credito_input, entrada_input, parcela_input, administradoras_multiselect],
335
  outputs=output_text
336
  )
337
 
338
-
339
  gr.Markdown("---")
340
  gr.Markdown("Ferramenta desenvolvida para otimizar a busca por consórcios de automóveis.")
341
 
342
  if __name__ == "__main__":
343
- demo.launch() # Você não precisa disso no Colab, mas sim no arquivo que você fará o deploy
 
3
  import io
4
  import itertools
5
  from collections import defaultdict
6
+ import gradio as gr
7
+ import os
8
+ from unicodedata import normalize
9
 
10
  # 🔧 CONFIGURAÇÕES GLOBAIS
 
 
11
  FILE_ID = "1EoH8V3Fyls0mNa95VcBN6ZL1B552ty3X"
12
  URL = f"https://drive.google.com/uc?export=download&id={FILE_ID}"
13
 
14
  # Variável global para armazenar o DataFrame
15
  df = None
16
 
17
+ def padronizar_nomes_administradoras(nome):
18
+ """Padroniza nomes de administradoras com mapeamento específico."""
19
+ if pd.isna(nome):
20
+ return nome
21
+
22
+ # Limpeza básica
23
+ nome_limpo = (
24
+ str(nome)
25
+ .strip()
26
+ .lower()
27
+ .replace('$', 's')
28
+ .replace(' ', ' ')
29
+ .strip()
30
+ )
31
+
32
+ # Remove acentos
33
+ nome_limpo = normalize('NFKD', nome_limpo).encode('ASCII', 'ignore').decode('ASCII')
34
+
35
+ # 👇 MAPEAMENTO COMPLETO com suas administradoras corretas
36
+ mapeamento = {
37
+ # Correções de nomes errados → nomes corretos
38
+ 'Ademicon': 'Ademicon',
39
+ 'ademicon': 'Ademicon',
40
+ 'ancora': 'Ancora',
41
+ 'banco do brasil': 'Banco do Brasil',
42
+ 'bb': 'Banco do Brasil',
43
+ 'Banrisul': 'Banrisul',
44
+ 'banrisul': 'Banrisul',
45
+ 'Bradesco': 'Bradesco',
46
+ 'bradesco': 'Bradesco',
47
+ 'caixa': 'Caixa XS5',
48
+ 'xs5': 'Caixa XS5',
49
+ 'caixa xs5': 'Caixa XS5',
50
+ 'caixa seguradora': 'Caixa XS5',
51
+ 'cnp': 'Caixa CNP',
52
+ 'Canopus': 'Canopus',
53
+ 'canopus': 'Canopus',
54
+ 'disal': 'Disal',
55
+ 'disal consorcios': 'Disal',
56
+ 'Embracon': 'Embracon',
57
+ 'embracon': 'Embracon',
58
+ 'ford': 'Ford',
59
+ 'honda': 'Honda',
60
+ 'hs': 'HS',
61
+ 'hs consorcio': 'HS',
62
+ 'Maggi': 'Maggi',
63
+ 'maggi': 'Maggi',
64
+ 'Mycon': 'Mycon',
65
+ 'mycon': 'Mycon',
66
+ 'porto': 'Porto Seguro',
67
+ 'porto-vp': 'Porto Seguro VP',
68
+ 'remaza': 'Remaza',
69
+ 'Rodebens': 'Rodobens',
70
+ 'rodobens': 'Rodobens',
71
+ 'Santander': 'Santander',
72
+ 'santander': 'Santander',
73
+ 'scania': 'Scania',
74
+ 'Serello': 'Serello',
75
+ 'serello': 'Serello',
76
+ 'servopa': 'Servopa',
77
+ 'sicoob unicoob': 'Sicoob Unicoob',
78
+ 'sicoobu': 'Sicoob Unicoob',
79
+ 'Sicredi': 'Sicredi',
80
+ 'sicredi': 'Sicredi',
81
+ 'Sponchiado': 'Sponchiado',
82
+ 'sponchiado': 'Sponchiado',
83
+ 'unifisa': 'Unifisa',
84
+ 'Volkswagen': 'Volkswagen',
85
+ 'volkswagen': 'Volkswagen',
86
+ 'yamaha': 'Yamaha',
87
+ 'zema': 'Zema'
88
+ }
89
+
90
+ return mapeamento.get(nome_limpo, nome_limpo)
91
+
92
+ def verificar_administradoras():
93
+ """Mostra como ficaram as administradoras após a padronização."""
94
+ if df is not None and 'administradora' in df.columns:
95
+ print("🔍 Administradoras únicas após padronização:")
96
+ administradoras_unicas = df[df['tipo_do_bem'].str.lower() == 'auto']['administradora'].unique()
97
+ for i, adm in enumerate(sorted(administradoras_unicas), 1):
98
+ print(f"{i:2d}. {adm}")
99
+ print(f"Total: {len(administradoras_unicas)} administradoras")
100
 
101
  def carregar_planilha():
102
  """Carrega e prepara a planilha para análise de consórcios de automóveis."""
 
111
  # Padroniza nomes das colunas
112
  df.columns = df.columns.str.strip().str.lower().str.replace(" ", "_")
113
 
114
+ # 👇 NOVA PADRONIZAÇÃO DAS ADMINISTRADORAS
115
  if 'administradora' in df.columns:
116
+ # Aplica a padronização personalizada
117
+ df['administradora'] = df['administradora'].apply(padronizar_nomes_administradoras)
118
 
119
  # Converte colunas numéricas
120
  numeric_cols = ['valor_credito', 'valor_entrada', 'valor_parcela', 'saldo_devedor', 'qtd_parcelas']
 
129
  # Remove duplicatas
130
  df.drop_duplicates(inplace=True)
131
  print("✅ Planilha carregada com sucesso!")
132
+
133
+ # Mostra as administradoras padronizadas
134
+ verificar_administradoras()
135
+
136
  return True
137
 
138
  except Exception as e:
139
  print(f"❌ Erro ao carregar planilha: {str(e)}")
 
140
  return False
141
 
142
  def agrupar_por_administradora(dataframe):
 
158
  if administradoras:
159
  if isinstance(administradoras, tuple):
160
  administradoras = list(administradoras)
161
+ administradoras = [a.strip() for a in administradoras]
162
  df_filtrado = df_filtrado[df_filtrado['administradora'].isin(administradoras)]
163
 
164
  if df_filtrado.empty:
 
171
 
172
  grupos = agrupar_por_administradora(df_filtrado)
173
  resultados = []
174
+ MAX_COMBINACOES_POR_ADM = 50
175
 
176
  for adm, cotas in grupos.items():
177
+ cotas = cotas[:25]
178
  combinacoes_validas = 0
179
 
180
+ for r in range(1, min(6, len(cotas)) + 1):
181
  for combinacao in itertools.combinations(cotas, r):
182
  if combinacoes_validas >= MAX_COMBINACOES_POR_ADM:
183
  break
 
201
  if not resultados:
202
  return "Nenhuma combinação adequada encontrada para automóveis com os parâmetros fornecidos."
203
 
 
204
  resultados.sort(key=lambda x: x[2])
 
 
205
  resultados = resultados[:250]
206
 
207
  return formatar_resultados(resultados)
 
213
  """Formata os resultados para exibição."""
214
  resposta = ""
215
 
 
 
 
216
  grouped_by_adm = defaultdict(list)
217
  for adm, combinacao, diferenca in resultados:
218
  grouped_by_adm[adm].append((combinacao, diferenca))
219
 
 
220
  for adm in grouped_by_adm:
221
+ grouped_by_adm[adm].sort(key=lambda x: x[1])
222
 
223
  final_resultados_diversificados = []
 
 
 
224
  adm_indices = {adm: 0 for adm in grouped_by_adm.keys()}
 
 
225
  active_adms = list(grouped_by_adm.keys())
226
+ current_adm_idx = 0
227
 
 
 
 
228
  while len(final_resultados_diversificados) < 15 and active_adms:
 
229
  adm_da_vez = active_adms[current_adm_idx]
 
 
230
  idx_na_adm = adm_indices[adm_da_vez]
231
 
 
232
  if idx_na_adm < len(grouped_by_adm[adm_da_vez]):
 
233
  combinacao_data, _ = grouped_by_adm[adm_da_vez][idx_na_adm]
 
 
234
  final_resultados_diversificados.append((adm_da_vez, combinacao_data))
 
 
235
  adm_indices[adm_da_vez] += 1
236
  else:
 
237
  active_adms.pop(current_adm_idx)
 
238
  if current_adm_idx >= len(active_adms) and active_adms:
239
+ current_adm_idx = 0
240
 
241
+ if active_adms:
 
242
  current_adm_idx = (current_adm_idx + 1) % len(active_adms)
243
  else:
244
+ break
245
 
 
246
  for idx, (adm, combinacao) in enumerate(final_resultados_diversificados, 1):
247
  codigos = [str(c.get('codigo', 'N/A')) for c in combinacao]
248
  total_credito = sum(c['valor_credito'] for c in combinacao)
 
258
  taxa_percentual = (taxa_total / credito_real) * 100 if credito_real > 0 else 0
259
  taxa_mensal = taxa_percentual / maior_prazo if maior_prazo > 0 else 0
260
 
 
 
261
  resposta += f"""
262
  --- COMBINAÇÃO {idx} ---
263
+ 🏢 Administradora: {adm.upper()}
264
  🔢 Códigos: {', '.join(codigos)}
265
  💰 Crédito Total: {formatar_moeda(total_credito)}
266
+ 💵 Entrada (+comissão): {formatar_moeda(total_entrada)}
267
  📉 Saldo Devedor: {formatar_moeda(total_saldo)}
268
  💲 Parcela: {formatar_moeda(total_parcelas)}
269
  ⏳ Prazo: {maior_prazo} meses
 
276
  """
277
  return resposta
278
 
 
 
 
 
 
 
 
279
  def ajustar_e_buscar(credito_desejado, entrada_desejada, parcela_desejada, administradoras):
280
  entrada_calculada = credito_desejado * 0.085 + entrada_desejada
281
  return buscar_combinacoes(credito_desejado, entrada_calculada, parcela_desejada, administradoras)
282
 
 
283
  def get_administradoras_options():
284
+ """Obtém lista de administradoras únicas e padronizadas para o frontend."""
285
+ if df is not None and 'administradora' in df.columns:
286
+ adm_list = (
287
+ df[df['tipo_do_bem'].str.lower() == 'auto']['administradora']
288
+ .dropna()
289
+ .unique()
290
+ )
291
+ return sorted(adm_list.tolist())
292
  return []
293
 
294
+ # --- INICIALIZAÇÃO PARA GRADIO ---
295
+ if not carregar_planilha():
296
+ print("⚠️ Aviso: Planilha não carregada na inicialização. Tentará carregar na primeira busca.")
297
+
298
  # --- Interface Gradio ---
299
  with gr.Blocks(title="Sistema de Busca de Consórcio de Automóveis") as demo:
300
  gr.HTML("""
 
309
  color: black !important;
310
  }
311
 
 
312
  input[type="number"] {
313
  background-color: white !important;
314
  border: 2px solid #000 !important;
 
318
  font-weight: 500;
319
  }
320
 
 
321
  input[type="text"], textarea {
322
  background-color: white !important;
323
  border: 2px solid black !important;
 
375
  administradoras_multiselect = gr.CheckboxGroup(
376
  label="Filtrar por Administradoras",
377
  choices=administradoras_options,
378
+ value=administradoras_options,
379
  interactive=True
380
  )
381
 
 
383
 
384
  output_text = gr.Textbox(label="Resultados da Busca", lines=20, max_lines=40, interactive=False, placeholder="Os resultados aparecerão aqui...")
385
 
 
386
  submit_btn.click(
387
  fn=ajustar_e_buscar,
388
  inputs=[credito_input, entrada_input, parcela_input, administradoras_multiselect],
389
  outputs=output_text
390
  )
391
 
 
392
  gr.Markdown("---")
393
  gr.Markdown("Ferramenta desenvolvida para otimizar a busca por consórcios de automóveis.")
394
 
395
  if __name__ == "__main__":
396
+ demo.launch()