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

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +267 -159
app.py CHANGED
@@ -3,22 +3,23 @@ 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."""
21
- global df
22
  try:
23
  print("⏳ Carregando planilha...")
24
  response = requests.get(URL)
@@ -33,7 +34,6 @@ def carregar_planilha():
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,33 +46,127 @@ def carregar_planilha():
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,34 +178,48 @@ def buscar_combinacoes(credito_desejado, entrada_desejada, parcela_desejada, adm
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,73 +227,44 @@ def buscar_combinacoes(credito_desejado, entrada_desejada, parcela_desejada, adm
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,46 +279,39 @@ 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
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,32 +320,38 @@ with gr.Blocks(title="Sistema de Busca de Consórcio de Automóveis") as demo:
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,63 +359,93 @@ with gr.Blocks(title="Sistema de Busca de Consórcio de Automóveis") as demo:
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
 
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
  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
 
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
  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
  # 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
  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
  }
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
  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)