Arthur-Attili commited on
Commit
126481d
·
verified ·
1 Parent(s): a9459fc

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +159 -267
app.py CHANGED
@@ -3,23 +3,22 @@ import requests
3
  import io
4
  import itertools
5
  from collections import defaultdict
6
- import gradio as gr
7
- import time
8
- import numpy as np
9
- from concurrent.futures import ThreadPoolExecutor, as_completed
10
- import heapq
11
 
12
  # 🔧 CONFIGURAÇÕES GLOBAIS
 
 
13
  FILE_ID = "1EoH8V3Fyls0mNa95VcBN6ZL1B552ty3X"
14
  URL = f"https://drive.google.com/uc?export=download&id={FILE_ID}"
15
 
16
  # Variável global para armazenar o DataFrame
17
  df = None
18
- df_filtrado_auto = None
19
 
20
  def carregar_planilha():
21
  """Carrega e prepara a planilha para análise de consórcios de automóveis."""
22
- global df, df_filtrado_auto
23
  try:
24
  print("⏳ Carregando planilha...")
25
  response = requests.get(URL)
@@ -34,6 +33,7 @@ def carregar_planilha():
34
  if 'administradora' in df.columns:
35
  df['administradora'] = df['administradora'].astype(str).str.strip().str.lower()
36
 
 
37
  # Converte colunas numéricas
38
  numeric_cols = ['valor_credito', 'valor_entrada', 'valor_parcela', 'saldo_devedor', 'qtd_parcelas']
39
  for col in numeric_cols:
@@ -46,127 +46,33 @@ def carregar_planilha():
46
 
47
  # Remove duplicatas
48
  df.drop_duplicates(inplace=True)
49
-
50
- # Pré-filtra dados de automóveis para otimização
51
- df_filtrado_auto = df[df['tipo_do_bem'].str.lower() == 'auto'].copy()
52
-
53
- # Pré-calcula valores importantes
54
- df_filtrado_auto['entrada_com_comissao'] = df_filtrado_auto['valor_entrada'] + (df_filtrado_auto['valor_credito'] * 0.085)
55
-
56
  print("✅ Planilha carregada com sucesso!")
57
  return True
58
 
59
  except Exception as e:
60
  print(f"❌ Erro ao carregar planilha: {str(e)}")
 
61
  return False
62
 
63
- def agrupar_por_administradora_otimizado(dataframe, credito_min, credito_max, entrada_min, entrada_max, parcela_min, parcela_max):
64
- """Agrupa cotas por administradora com pré-filtro otimizado."""
65
  grupos = defaultdict(list)
66
-
67
- # Pré-filtra as cotas que estão dentro das faixas individuais
68
  for _, row in dataframe.iterrows():
69
- credito = row['valor_credito']
70
- entrada = row['entrada_com_comissao']
71
- parcela = row['valor_parcela']
72
-
73
- # Verifica se a cota individual está dentro das faixas aceitáveis
74
- if (credito_min * 0.1 <= credito <= credito_max * 2.0 and
75
- entrada_min * 0.1 <= entrada <= entrada_max * 2.0 and
76
- parcela_min * 0.1 <= parcela <= parcela_max * 3.0):
77
-
78
- grupos[row['administradora']].append(row.to_dict())
79
-
80
  return grupos
81
 
82
- def encontrar_combinacoes_otimizado(cotas, credito_min, credito_max, entrada_min, entrada_max, parcela_min, parcela_max, max_combinacoes=1000):
83
- """Encontra combinações de forma otimizada usando poda de busca."""
84
- resultados = []
85
- n = len(cotas)
86
-
87
- # Ordena as cotas por valor de crédito para poda mais eficiente
88
- cotas_ordenadas = sorted(cotas, key=lambda x: x['valor_credito'])
89
-
90
- # Função recursiva com poda
91
- def backtrack(start, combinacao_atual, credito_total, entrada_total, parcela_total):
92
- # Poda: se já ultrapassou os limites máximos, não continua
93
- if credito_total > credito_max * 1.1 or entrada_total > entrada_max * 1.1 or parcela_total > parcela_max * 1.5:
94
- return
95
-
96
- # Se está dentro dos limites, adiciona ao resultado
97
- if (credito_min <= credito_total <= credito_max and
98
- entrada_min <= entrada_total <= entrada_max and
99
- parcela_min <= parcela_total <= parcela_max):
100
-
101
- # Calcula a diferença para ordenação
102
- saldo_total = sum(c['saldo_devedor'] for c in combinacao_atual)
103
- valor_final = entrada_total + saldo_total
104
- diferenca = abs(valor_final - credito_total)
105
-
106
- # Usa heap para manter apenas as melhores combinações
107
- if len(resultados) < max_combinacoes:
108
- heapq.heappush(resultados, (-diferenca, combinacao_atual[:]))
109
- else:
110
- # Substitui a pior combinação se esta for melhor
111
- heapq.heappushpop(resultados, (-diferenca, combinacao_atual[:]))
112
-
113
- # Limita o tamanho das combinações e continua a busca
114
- if len(combinacao_atual) < 10 and start < n:
115
- for i in range(start, n):
116
- cota = cotas_ordenadas[i]
117
- novo_credito = credito_total + cota['valor_credito']
118
- nova_entrada = entrada_total + cota['entrada_com_comissao']
119
- nova_parcela = parcela_total + cota['valor_parcela']
120
-
121
- # Poda: se já ultrapassou muito os limites, não continua
122
- if novo_credito > credito_max * 1.5 or nova_entrada > entrada_max * 1.5:
123
- continue
124
-
125
- combinacao_atual.append(cota)
126
- backtrack(i + 1, combinacao_atual, novo_credito, nova_entrada, nova_parcela)
127
- combinacao_atual.pop()
128
-
129
- # Inicia a busca
130
- backtrack(0, [], 0, 0, 0)
131
-
132
- # Converte o heap para lista ordenada
133
- resultados_ordenados = []
134
- while resultados:
135
- diferenca, combinacao = heapq.heappop(resultados)
136
- resultados_ordenados.append((combinacao, -diferenca))
137
-
138
- return resultados_ordenados
139
-
140
- def processar_administradora_10_cotas(args):
141
- """Processa uma administradora para combinações de até 10 cotas."""
142
- adm, cotas, credito_min, credito_max, entrada_min, entrada_max, parcela_min, parcela_max = args
143
- resultados_adm = []
144
-
145
- # Limita para as 20 melhores cotas por administradora
146
- cotas_limitadas = cotas[:20]
147
-
148
- # Encontra combinações de forma otimizada
149
- combinacoes = encontrar_combinacoes_otimizado(
150
- cotas_limitadas, credito_min, credito_max, entrada_min, entrada_max,
151
- parcela_min, parcela_max, max_combinacoes=100
152
- )
153
-
154
- for combinacao, diferenca in combinacoes:
155
- resultados_adm.append((adm, combinacao, diferenca))
156
-
157
- return resultados_adm
158
-
159
- def buscar_combinacoes_10_cotas(credito_desejado, entrada_desejada, parcela_desejada, administradoras=None):
160
- """Busca combinações de até 10 cotas de forma otimizada."""
161
- global df_filtrado_auto
162
-
163
- if df_filtrado_auto is None:
164
  if not carregar_planilha():
165
  return "❌ Erro: Planilha não carregada. Verifique se o ID do arquivo está correto e público."
166
 
167
- # Filtra por administradoras selecionadas
168
- df_filtrado = df_filtrado_auto.copy()
169
  if administradoras:
 
 
170
  administradoras = [a.strip().lower() for a in administradoras]
171
  df_filtrado = df_filtrado[df_filtrado['administradora'].isin(administradoras)]
172
 
@@ -178,48 +84,34 @@ def buscar_combinacoes_10_cotas(credito_desejado, entrada_desejada, parcela_dese
178
  entrada_min, entrada_max = entrada_desejada * 0.93, entrada_desejada * 1.07
179
  parcela_min, parcela_max = parcela_desejada * 0.5, parcela_desejada * 1.5
180
 
181
- # Agrupa com pré-filtro otimizado
182
- grupos = agrupar_por_administradora_otimizado(
183
- df_filtrado, credito_min, credito_max, entrada_min, entrada_max, parcela_min, parcela_max
184
- )
185
-
186
- # Prepara argumentos para processamento paralelo
187
- args_list = []
188
- for adm, cotas in grupos.items():
189
- if cotas: # Só processa administradoras com cotas
190
- args_list.append((adm, cotas, credito_min, credito_max, entrada_min, entrada_max, parcela_min, parcela_max))
191
-
192
  resultados = []
193
-
194
- # Processa em paralelo
195
- if args_list:
196
- with ThreadPoolExecutor(max_workers=min(4, len(args_list))) as executor:
197
- futures = [executor.submit(processar_administradora_10_cotas, args) for args in args_list]
198
- for future in as_completed(futures):
199
- resultados.extend(future.result())
200
 
201
- if not resultados:
202
- # Tenta com faixas mais amplas se não encontrou resultados
203
- credito_min, credito_max = credito_desejado * 0.90, credito_desejado * 1.10
204
- entrada_min, entrada_max = entrada_desejada * 0.90, entrada_desejada * 1.10
205
-
206
- grupos = agrupar_por_administradora_otimizado(
207
- df_filtrado, credito_min, credito_max, entrada_min, entrada_max, parcela_min, parcela_max
208
- )
209
 
210
- for adm, cotas in grupos.items():
211
- if not cotas:
212
- continue
213
-
214
- cotas = cotas[:20] # Limita a 20 cotas por administradora
215
-
216
- combinacoes = encontrar_combinacoes_otimizado(
217
- cotas, credito_min, credito_max, entrada_min, entrada_max,
218
- parcela_min, parcela_max, max_combinacoes=50
219
- )
220
-
221
- for combinacao, diferenca in combinacoes:
222
- resultados.append((adm, combinacao, diferenca))
 
 
 
 
 
 
 
223
 
224
  if not resultados:
225
  return "Nenhuma combinação adequada encontrada para automóveis com os parâmetros fornecidos."
@@ -227,44 +119,73 @@ def buscar_combinacoes_10_cotas(credito_desejado, entrada_desejada, parcela_dese
227
  # Ordena por diferença (melhores combinações primeiro)
228
  resultados.sort(key=lambda x: x[2])
229
 
230
- # Limita o número total de resultados
231
- resultados = resultados[:200]
232
 
233
- return formatar_resultados_10_cotas(resultados)
234
 
235
  def formatar_moeda(valor):
236
  return f"R$ {valor:,.2f}".replace(",", "v").replace(".", ",").replace("v", ".")
237
 
238
- def formatar_resultados_10_cotas(resultados):
239
  """Formata os resultados para exibição."""
240
- if not resultados:
241
- return "Nenhuma combinação encontrada."
242
-
243
- resposta = f"📊 **Total de {len(resultados)} combinações encontradas (1-10 cotas)**\n\n"
244
 
245
- # Agrupa por administradora mantendo a ordenação por qualidade
 
 
246
  grouped_by_adm = defaultdict(list)
247
  for adm, combinacao, diferenca in resultados:
248
  grouped_by_adm[adm].append((combinacao, diferenca))
249
 
250
- # Ordena cada grupo por diferença
251
  for adm in grouped_by_adm:
252
- grouped_by_adm[adm].sort(key=lambda x: x[1])
253
-
254
- # Seleciona as melhores combinações de cada administradora
255
- final_resultados = []
256
- for adm, combinacoes in grouped_by_adm.items():
257
- # Pega as 5 melhores de cada administradora
258
- for combinacao, diferenca in combinacoes[:5]:
259
- final_resultados.append((adm, combinacao, diferenca))
260
 
261
- # Ordena todas as combinações por qualidade
262
- final_resultados.sort(key=lambda x: x[2])
 
 
 
 
263
 
264
- # Limita a 20 resultados finais para exibição
265
- final_resultados = final_resultados[:20]
266
 
267
- for idx, (adm, combinacao, diferenca) in enumerate(final_resultados, 1):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
268
  codigos = [str(c.get('codigo', 'N/A')) for c in combinacao]
269
  total_credito = sum(c['valor_credito'] for c in combinacao)
270
  total_entrada_base = sum(c['valor_entrada'] for c in combinacao)
@@ -279,39 +200,46 @@ def formatar_resultados_10_cotas(resultados):
279
  taxa_percentual = (taxa_total / credito_real) * 100 if credito_real > 0 else 0
280
  taxa_mensal = taxa_percentual / maior_prazo if maior_prazo > 0 else 0
281
 
 
 
282
  resposta += f"""
283
  --- COMBINAÇÃO {idx} ---
284
- 🏢 **Administradora:** {adm.upper()}
285
- 🔢 **Códigos ({len(combinacao)} cotas):** {', '.join(codigos)}
286
- 💰 **Crédito Total:** {formatar_moeda(total_credito)}
287
- 💵 **Entrada (+comissão):** {formatar_moeda(total_entrada)}
288
- 📉 **Saldo Devedor:** {formatar_moeda(total_saldo)}
289
- 💲 **Parcela:** {formatar_moeda(total_parcelas)}
290
- **Prazo:** {maior_prazo} meses
291
- 🔚 **Valor Final:** {formatar_moeda(valor_final)}
292
- 💠 **Crédito Líquido:** {formatar_moeda(credito_real)}
293
- 📊 **Taxa Total:** {taxa_percentual:.2f}% ({formatar_moeda(taxa_total)})
294
- 🗓️ **Taxa Mensal:** {taxa_mensal:.2f}%
295
- **Score de Qualidade:** {diferenca:.2f}
 
296
  """
297
  return resposta
298
 
299
  # --- INICIALIZAÇÃO PARA GRADIO ---
 
 
300
  if not carregar_planilha():
301
  print("⚠️ Aviso: Planilha não carregada na inicialização. Tentará carregar na primeira busca.")
302
 
303
- def ajustar_e_buscar_10_cotas(credito_desejado, entrada_desejada, parcela_desejada, administradoras):
 
304
  entrada_calculada = credito_desejado * 0.085 + entrada_desejada
305
- return buscar_combinacoes_10_cotas(credito_desejado, entrada_calculada, parcela_desejada, administradoras)
306
 
 
307
  def get_administradoras_options():
308
- if df is not None and df_filtrado_auto is not None:
309
- adm_list = df_filtrado_auto['administradora'].unique()
310
- return sorted(adm_list.tolist())
311
  return []
312
 
313
- # --- Interface Gradio para 10 cotas ---
314
- with gr.Blocks(title="Sistema de Busca de Consórcio de Automóveis (1-10 Cotas)") as demo:
315
  gr.HTML("""
316
  <link href="https://fonts.googleapis.com/css2?family=Poppins&display=swap" rel="stylesheet">
317
  <style>
@@ -320,38 +248,32 @@ with gr.Blocks(title="Sistema de Busca de Consórcio de Automóveis (1-10 Cotas)
320
  }
321
 
322
  body {
323
- background: linear-gradient(135deg, #ff8f00 0%, #ffa000 100%) !important;
324
  color: black !important;
325
  }
326
 
327
- .gr-box {
328
- border-radius: 12px !important;
329
- border: 2px solid #000 !important;
330
- }
331
-
332
  input[type="number"] {
333
  background-color: white !important;
334
  border: 2px solid #000 !important;
335
  border-radius: 12px !important;
336
  color: black !important;
337
- padding: 12px !important;
338
  font-weight: 500;
339
  }
340
 
341
- textarea {
 
342
  background-color: white !important;
343
  border: 2px solid black !important;
344
  border-radius: 12px !important;
345
  color: black !important;
346
- padding: 12px !important;
347
- font-size: 14px;
348
  }
349
 
350
- .gr-checkbox-group {
351
- background-color: white !important;
352
- border: 2px solid black !important;
353
- border-radius: 12px !important;
354
- padding: 10px;
355
  }
356
 
357
  .gr-checkbox label {
@@ -359,93 +281,63 @@ with gr.Blocks(title="Sistema de Busca de Consórcio de Automóveis (1-10 Cotas)
359
  align-items: center;
360
  gap: 8px;
361
  font-weight: 600;
362
- margin: 5px 0;
363
  }
364
 
365
  input[type="checkbox"] {
366
- accent-color: #ff8f00 !important;
367
  width: 18px;
368
  height: 18px;
369
  }
370
 
371
  .gr-button {
372
  font-weight: bold;
373
- border-radius: 12px !important;
374
- background: linear-gradient(135deg, #000000 0%, #333333 100%) !important;
375
- color: white !important;
376
- border: none !important;
377
- padding: 12px 24px !important;
378
- font-size: 16px;
379
  }
380
 
381
  .gr-button:hover {
382
- background: linear-gradient(135deg, #333333 0%, #555555 100%) !important;
383
- transform: translateY(-2px);
384
- box-shadow: 0 4px 8px rgba(0,0,0,0.2);
385
  }
386
 
387
  label {
388
- font-weight: 700 !important;
389
- color: #000 !important;
390
- margin-bottom: 8px !important;
391
- }
392
-
393
- .gr-markdown h1 {
394
- color: #000 !important;
395
- text-align: center;
396
- margin-bottom: 20px;
397
  }
398
  </style>
399
  """)
400
 
401
  gr.Markdown("# 🚗 Sistema de Busca de Combinações de Consórcio (Automóveis)")
402
- gr.Markdown("### 🔥 **Versão 1-10 Cotas - Busca Inteligente**")
403
- gr.Markdown("Preencha os campos abaixo para encontrar as melhores combinações de 1 a 10 cotas de consórcio de veículo.")
404
 
405
  with gr.Row():
406
- with gr.Column(scale=1):
407
- credito_input = gr.Number(label="Crédito Desejado (R$)", value=30000, step=1000, interactive=True)
408
- entrada_input = gr.Number(label="Entrada Desejada (R$)", value=3000, step=100, interactive=True)
409
- parcela_input = gr.Number(label="Parcela Desejada (R$)", value=500, step=10, interactive=True)
410
-
411
- gr.Markdown("### ⚙️ Configurações de Busca")
412
- gr.Markdown("O sistema busca automaticamente as melhores combinações de 1 a 10 cotas")
413
-
414
- submit_btn = gr.Button("🔍 Buscar Combinações (1-10 Cotas)", variant="primary", size="lg")
415
-
416
- with gr.Column(scale=1):
417
- administradoras_options = get_administradoras_options()
418
- administradoras_multiselect = gr.CheckboxGroup(
419
- label="Filtrar por Administradoras",
420
- choices=administradoras_options,
421
- value=administradoras_options[:5] if len(administradoras_options) > 5 else administradoras_options,
422
- interactive=True
423
- )
424
-
425
- gr.Markdown("### 💡 Dicas para busca eficiente:")
426
- gr.Markdown("- Selecione até 5 administradoras para busca mais rápida")
427
- gr.Markdown("- Use valores aproximados para encontrar mais opções")
428
- gr.Markdown("- O sistema mostra até 20 das melhores combinações")
429
-
430
- output_text = gr.Textbox(
431
- label="📋 Resultados da Busca (1-10 Cotas)",
432
- lines=25,
433
- max_lines=50,
434
- interactive=False,
435
- placeholder="Os resultados aparecerão aqui...",
436
- show_copy_button=True
437
  )
438
 
439
- # Conecta o botão à função de busca para 10 cotas
 
 
 
 
440
  submit_btn.click(
441
- fn=ajustar_e_buscar_10_cotas,
442
  inputs=[credito_input, entrada_input, parcela_input, administradoras_multiselect],
443
  outputs=output_text
444
  )
445
 
 
446
  gr.Markdown("---")
447
- gr.Markdown("**🔥 Ferramenta avançada** para buscas de combinações de 1 a 10 cotas")
448
- gr.Markdown("*Desenvolvido com algoritmos de busca inteligente e poda*")
449
 
450
  if __name__ == "__main__":
451
- demo.launch(share=False)
 
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."""
21
+ global df
22
  try:
23
  print("⏳ Carregando planilha...")
24
  response = requests.get(URL)
 
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']
39
  for col in numeric_cols:
 
46
 
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):
58
+ """Agrupa cotas por administradora."""
59
  grupos = defaultdict(list)
 
 
60
  for _, row in dataframe.iterrows():
61
+ grupos[row['administradora']].append(row.to_dict())
 
 
 
 
 
 
 
 
 
 
62
  return grupos
63
 
64
+ def buscar_combinacoes(credito_desejado, entrada_desejada, parcela_desejada, administradoras=None):
65
+ """Busca combinações de cotas para automóveis dentro dos parâmetros especificados."""
66
+ global df
67
+ if df is None:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68
  if not carregar_planilha():
69
  return "❌ Erro: Planilha não carregada. Verifique se o ID do arquivo está correto e público."
70
 
71
+ # Filtra por tipo de bem (Auto) e administradoras selecionadas
72
+ df_filtrado = df[df['tipo_do_bem'].str.lower() == 'auto']
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
 
 
84
  entrada_min, entrada_max = entrada_desejada * 0.93, entrada_desejada * 1.07
85
  parcela_min, parcela_max = parcela_desejada * 0.5, parcela_desejada * 1.5
86
 
87
+ grupos = agrupar_por_administradora(df_filtrado)
 
 
 
 
 
 
 
 
 
 
88
  resultados = []
89
+ MAX_COMBINACOES_POR_ADM = 1000 # Limite para evitar processamento excessivo
 
 
 
 
 
 
90
 
91
+ for adm, cotas in grupos.items():
92
+ cotas = cotas[:20] # Limita a 20 cotas por administradora para evitar combinações explosivas
93
+ combinacoes_validas = 0
 
 
 
 
 
94
 
95
+ for r in range(1, min(5, 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
99
+
100
+ total_credito = sum(c['valor_credito'] for c in combinacao)
101
+ total_entrada_base = sum(c['valor_entrada'] for c in combinacao)
102
+ total_entrada = total_entrada_base + (total_credito * 0.085)
103
+ total_parcelas = sum(c['valor_parcela'] for c in combinacao)
104
+
105
+ if (credito_min <= total_credito <= credito_max and
106
+ entrada_min <= total_entrada <= entrada_max and
107
+ parcela_min <= total_parcelas <= parcela_max):
108
+
109
+ total_saldo = sum(c['saldo_devedor'] for c in combinacao)
110
+ valor_final = round(total_entrada + total_saldo, 2)
111
+ diferenca = abs(valor_final - total_credito)
112
+
113
+ resultados.append((adm, combinacao, diferenca))
114
+ combinacoes_validas += 1
115
 
116
  if not resultados:
117
  return "Nenhuma combinação adequada encontrada para automóveis com os parâmetros fornecidos."
 
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[:500]
124
 
125
+ return formatar_resultados(resultados)
126
 
127
  def formatar_moeda(valor):
128
  return f"R$ {valor:,.2f}".replace(",", "v").replace(".", ",").replace("v", ".")
129
 
130
+ 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)
191
  total_entrada_base = sum(c['valor_entrada'] for c in combinacao)
 
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
214
+ 🔚 Valor Final: {formatar_moeda(valor_final)}
215
+ 💠 Crédito Líquido: {formatar_moeda(credito_real)}
216
+ 📊 Taxa Total: {taxa_percentual:.2f}% ({formatar_moeda(taxa_total)})
217
+ 🗓️ Taxa Mensal: {taxa_mensal:.2f}%
218
+ 🗓️ Taxa Anual: {taxa_mensal * 12:.2f}%
219
+ 📊 Nº de Cotas: {len(combinacao)}
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("""
244
  <link href="https://fonts.googleapis.com/css2?family=Poppins&display=swap" rel="stylesheet">
245
  <style>
 
248
  }
249
 
250
  body {
251
+ background-color: #ff8f00 !important;
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;
259
  border-radius: 12px !important;
260
  color: black !important;
261
+ padding: 10px !important;
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;
269
  border-radius: 12px !important;
270
  color: black !important;
271
+ padding: 10px !important;
 
272
  }
273
 
274
+ input::placeholder, textarea::placeholder {
275
+ color: #333 !important;
276
+ opacity: 0.7;
 
 
277
  }
278
 
279
  .gr-checkbox label {
 
281
  align-items: center;
282
  gap: 8px;
283
  font-weight: 600;
 
284
  }
285
 
286
  input[type="checkbox"] {
287
+ accent-color: black !important;
288
  width: 18px;
289
  height: 18px;
290
  }
291
 
292
  .gr-button {
293
  font-weight: bold;
294
+ border-radius: 10px !important;
295
+ background-color: white !important;
296
+ color: black !important;
297
+ border: 2px solid black !important;
 
 
298
  }
299
 
300
  .gr-button:hover {
301
+ background-color: #fff3e0 !important;
302
+ color: black !important;
 
303
  }
304
 
305
  label {
306
+ font-weight: 600 !important;
 
 
 
 
 
 
 
 
307
  }
308
  </style>
309
  """)
310
 
311
  gr.Markdown("# 🚗 Sistema de Busca de Combinações de Consórcio (Automóveis)")
312
+ gr.Markdown("Preencha os campos abaixo para encontrar as melhores combinações de cotas de consórcio de veículo.")
 
313
 
314
  with gr.Row():
315
+ credito_input = gr.Number(label="Crédito Desejado (R$)", value=30000, step=1000, interactive=True)
316
+ entrada_input = gr.Number(label="Entrada Desejada (R$)", value=3000, step=100, interactive=True)
317
+ parcela_input = gr.Number(label="Parcela Desejada (R$)", value=500, step=10, interactive=True)
318
+
319
+ administradoras_options = get_administradoras_options()
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
 
327
+ submit_btn = gr.Button("🔍 Buscar Combinações", variant="primary")
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