Arthur-Attili commited on
Commit
89fba12
·
verified ·
1 Parent(s): 3a44587

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +317 -211
app.py CHANGED
@@ -1,8 +1,18 @@
 
 
 
 
 
 
 
 
 
1
  import pandas as pd
2
  import requests
3
  import io
4
  import itertools
5
  from collections import defaultdict
 
6
  import time
7
  import math
8
 
@@ -26,17 +36,34 @@ def carregar_planilha():
26
 
27
  try:
28
  print("⏳ Carregando planilha...")
29
- response = requests.get(URL, timeout=30)
30
- response.raise_for_status()
31
- data = io.BytesIO(response.content)
32
- df = pd.read_excel(data)
33
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
  # Padroniza nomes das colunas
35
  df.columns = df.columns.str.strip().str.lower().str.replace(" ", "_")
36
-
37
- # Padroniza os nomes das administradoras
38
- if 'administradora' in df.columns:
39
- df['administradora'] = df['administradora'].astype(str).str.strip().str.lower()
40
 
41
  # Converte colunas numéricas
42
  colunas_numericas = ['valor_credito', 'valor_entrada', 'valor_parcela', 'saldo_devedor', 'qtd_parcelas']
@@ -45,15 +72,37 @@ def carregar_planilha():
45
  df[coluna] = pd.to_numeric(df[coluna], errors='coerce').fillna(0)
46
 
47
  # Verifica colunas essenciais
48
- if 'tipo_do_bem' not in df.columns or 'administradora' not in df.columns:
49
- raise ValueError("Colunas essenciais não encontradas")
 
 
50
 
51
  # Remove duplicatas
52
  df.drop_duplicates(inplace=True)
 
53
 
54
  # Pré-filtra e pré-processa dados de auto
55
- df_auto = df[df['tipo_do_bem'].str.lower() == 'auto'].copy()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
  grupos_auto = agrupar_por_administradora(df_auto)
 
 
 
 
57
 
58
  planilha_carregada = True
59
  print("✅ Planilha carregada e pré-processada com sucesso")
@@ -63,13 +112,16 @@ def carregar_planilha():
63
 
64
  except Exception as e:
65
  print(f"❌ Erro ao carregar planilha: {str(e)}")
 
 
66
  return False
67
 
68
  def agrupar_por_administradora(dataframe):
69
  """Agrupa cotas por administradora."""
70
  grupos = defaultdict(list)
71
  for _, row in dataframe.iterrows():
72
- grupos[row['administradora']].append(row.to_dict())
 
73
  return grupos
74
 
75
  def calcular_tempo_parcelas(combinacao):
@@ -119,19 +171,31 @@ def calcular_tempo_parcelas(combinacao):
119
  print(f"❌ Erro ao calcular tempo de parcelas: {str(e)}")
120
  return ""
121
 
122
- def buscar_combinacoes_otimizado(credito_desejado, entrada_desejada, parcela_desejada, administradoras=None, max_combinacoes=15):
123
  """Busca combinações de cotas dentro dos parâmetros especificados de forma otimizada."""
124
  global df_auto, grupos_auto
125
 
 
 
 
 
 
 
 
 
 
 
 
126
  # Garante que o DataFrame está carregado
127
  if not planilha_carregada:
 
128
  if not carregar_planilha():
129
  return "❌ Erro: Planilha não carregada. Por favor, tente novamente."
130
 
131
  # Filtra administradoras se especificado
132
  if administradoras and len(administradoras) > 0:
133
  administradoras = [a.strip().lower() for a in administradoras]
134
- grupos_filtrados = {adm: cotas for adm, cotas in grupos_auto.items() if adm in administradoras}
135
  print(f"🔍 Filtrando por administradoras: {administradoras}")
136
  else:
137
  grupos_filtrados = grupos_auto
@@ -147,17 +211,17 @@ def buscar_combinacoes_otimizado(credito_desejado, entrada_desejada, parcela_des
147
  tolerancia_credito = 0.50 # 10%
148
  tolerancia_entrada = 0.50 # 10%
149
  tolerancia_parcela = 1.00 # 50%
150
- max_cotas_combinacao = 25 # Até 25 cotas por combinação
151
  elif credito_desejado > 100000: # Para valores acima de 100 Mil
152
  tolerancia_credito = 0.25 # 10%
153
  tolerancia_entrada = 0.25 # 10%
154
  tolerancia_parcela = 0.50 # 30%
155
- max_cotas_combinacao = 15 # Até 15 cotas por combinação
156
  else: # Para valores até 100k
157
  tolerancia_credito = 0.15 # 5%
158
  tolerancia_entrada = 0.15 # 5%
159
  tolerancia_parcela = 0.30 # 25%
160
- max_cotas_combinacao = 15 # Até 15 cotas por combinação
161
 
162
  # Define faixas de tolerância
163
  credito_min = credito_desejado * (1 - tolerancia_credito)
@@ -167,11 +231,7 @@ def buscar_combinacoes_otimizado(credito_desejado, entrada_desejada, parcela_des
167
  parcela_min = parcela_desejada * (1 - tolerancia_parcela)
168
  parcela_max = parcela_desejada * (1 + tolerancia_parcela)
169
 
170
- print(f"💰 Valores desejados: Crédito: R$ {credito_desejado:,.2f}, Entrada: R$ {entrada_desejada:,.2f}, Parcela: R$ {parcela_desejada:,.2f}")
171
  print(f"🔧 Tolerâncias: Crédito: ±{tolerancia_credito*100:.1f}%, Entrada: ±{tolerancia_entrada*100:.1f}%, Parcela: ±{tolerancia_parcela*100:.1f}%")
172
- print(f"🔧 Faixas aceitáveis: Crédito: R$ {credito_min:,.2f} - R$ {credito_max:,.2f}")
173
- print(f"🔧 Faixas aceitáveis: Entrada: R$ {entrada_min:,.2f} - R$ {entrada_max:,.2f}")
174
- print(f"🔧 Faixas aceitáveis: Parcela: R$ {parcela_min:,.2f} - R$ {parcela_max:,.2f}")
175
  print(f"🔧 Máximo de cotas por combinação: {max_cotas_combinacao}")
176
 
177
  resultados = []
@@ -188,9 +248,10 @@ def buscar_combinacoes_otimizado(credito_desejado, entrada_desejada, parcela_des
188
  for r in range(1, min(max_cotas_combinacao + 1, len(cotas_para_combinar) + 1)):
189
  # Limita o número de combinações testadas por administradora
190
  if credito_desejado > 500000:
191
- max_combinacoes_por_adm = 5000
192
  else:
193
- max_combinacoes_por_adm = 2000
 
194
  combinacoes_geradas = 0
195
 
196
  for combinacao in itertools.combinations(cotas_para_combinar, r):
@@ -200,17 +261,22 @@ def buscar_combinacoes_otimizado(credito_desejado, entrada_desejada, parcela_des
200
  total_combinacoes_testadas += 1
201
  combinacoes_geradas += 1
202
 
203
- total_credito = sum(c['valor_credito'] for c in combinacao)
204
- total_entrada_base = sum(c['valor_entrada'] for c in combinacao)
 
 
 
 
 
205
  total_entrada = total_entrada_base + (total_credito * 0.085)
206
- total_parcelas = sum(c['valor_parcela'] for c in combinacao)
207
 
208
  # Verifica se a combinação está dentro das tolerâncias
209
  if (credito_min <= total_credito <= credito_max and
210
  entrada_min <= total_entrada <= entrada_max and
211
  parcela_min <= total_parcelas <= parcela_max):
212
 
213
- total_saldo = sum(c['saldo_devedor'] for c in combinacao)
214
  valor_final = round(total_entrada + total_saldo, 2)
215
  diferenca = abs(valor_final - total_credito)
216
 
@@ -220,9 +286,15 @@ def buscar_combinacoes_otimizado(credito_desejado, entrada_desejada, parcela_des
220
  if len(resultados) >= max_combinacoes * 3:
221
  break
222
 
 
 
 
223
  if len(resultados) >= max_combinacoes * 3:
224
  break
225
 
 
 
 
226
  if len(resultados) >= max_combinacoes * 3:
227
  break
228
 
@@ -238,14 +310,20 @@ def buscar_combinacoes_otimizado(credito_desejado, entrada_desejada, parcela_des
238
  resultados.sort(key=lambda x: x[2])
239
  return formatar_resultados(resultados[:max_combinacoes])
240
 
241
- def buscar_combinacoes_amplo(credito_desejado, entrada_desejada, parcela_desejada, administradoras=None, max_combinacoes=15):
242
  """Busca combinações com tolerâncias mais amplas para valores altos."""
243
  global grupos_auto
244
 
 
 
 
 
 
 
245
  # Filtra administradoras se especificado
246
  if administradoras and len(administradoras) > 0:
247
  administradoras = [a.strip().lower() for a in administradoras]
248
- grupos_filtrados = {adm: cotas for adm, cotas in grupos_auto.items() if adm in administradoras}
249
  else:
250
  grupos_filtrados = grupos_auto
251
 
@@ -254,17 +332,17 @@ def buscar_combinacoes_amplo(credito_desejado, entrada_desejada, parcela_desejad
254
  tolerancia_credito = 0.50 # 15%
255
  tolerancia_entrada = 0.50 # 15%
256
  tolerancia_parcela = 1.00 # 75%
257
- max_cotas_combinacao = 25 # Até 10 cotas por combinação
258
  elif credito_desejado > 100000:
259
  tolerancia_credito = 0.25 # 10%
260
  tolerancia_entrada = 0.25 # 10%
261
  tolerancia_parcela = 0.50 # 50%
262
- max_cotas_combinacao = 15 # Até 8 cotas por combinação
263
  else:
264
  tolerancia_credito = 0.15 # 5%
265
  tolerancia_entrada = 0.15 # 5%
266
  tolerancia_parcela = 0.30 # 35%
267
- max_cotas_combinacao = 15 # Até 6 cotas por combinação
268
 
269
  credito_min = credito_desejado * (1 - tolerancia_credito)
270
  credito_max = credito_desejado * (1 + tolerancia_credito)
@@ -285,7 +363,7 @@ def buscar_combinacoes_amplo(credito_desejado, entrada_desejada, parcela_desejad
285
  # Gera combinações
286
  for r in range(1, min(max_cotas_combinacao + 1, len(cotas) + 1)):
287
  combinacoes_geradas = 0
288
- max_combinacoes_por_adm = 5000
289
 
290
  for combinacao in itertools.combinations(cotas, r):
291
  if combinacoes_geradas >= max_combinacoes_por_adm:
@@ -294,16 +372,21 @@ def buscar_combinacoes_amplo(credito_desejado, entrada_desejada, parcela_desejad
294
  total_combinacoes_testadas += 1
295
  combinacoes_geradas += 1
296
 
297
- total_credito = sum(c['valor_credito'] for c in combinacao)
298
- total_entrada_base = sum(c['valor_entrada'] for c in combinacao)
 
 
 
 
 
299
  total_entrada = total_entrada_base + (total_credito * 0.085)
300
- total_parcelas = sum(c['valor_parcela'] for c in combinacao)
301
 
302
  if (credito_min <= total_credito <= credito_max and
303
  entrada_min <= total_entrada <= entrada_max and
304
  parcela_min <= total_parcelas <= parcela_max):
305
 
306
- total_saldo = sum(c['saldo_devedor'] for c in combinacao)
307
  valor_final = round(total_entrada + total_saldo, 2)
308
  diferenca = abs(valor_final - total_credito)
309
 
@@ -312,9 +395,15 @@ def buscar_combinacoes_amplo(credito_desejado, entrada_desejada, parcela_desejad
312
  if len(resultados) >= max_combinacoes * 2:
313
  break
314
 
 
 
 
315
  if len(resultados) >= max_combinacoes * 2:
316
  break
317
 
 
 
 
318
  if len(resultados) >= max_combinacoes * 2:
319
  break
320
 
@@ -328,7 +417,10 @@ def buscar_combinacoes_amplo(credito_desejado, entrada_desejada, parcela_desejad
328
  return formatar_resultados(resultados[:max_combinacoes])
329
 
330
  def formatar_moeda(valor):
331
- return f"R$ {valor:,.2f}".replace(",", "v").replace(".", ",").replace("v", ".")
 
 
 
332
 
333
  def formatar_resultados(resultados):
334
  """Formata os resultados para exibição INCLUINDO TEMPO DE PARCELAS CORRETO."""
@@ -337,13 +429,16 @@ def formatar_resultados(resultados):
337
 
338
  resposta = ""
339
  for idx, (adm, combinacao, _) in enumerate(resultados, 1):
340
- codigos = [str(c.get('codigo', 'N/A')) for c in combinacao]
341
- total_credito = sum(c['valor_credito'] for c in combinacao)
342
- total_entrada_base = sum(c['valor_entrada'] for c in combinacao)
343
  total_entrada = total_entrada_base + (total_credito * 0.085)
344
- total_saldo = sum(c['saldo_devedor'] for c in combinacao)
345
- total_parcelas = sum(c['valor_parcela'] for c in combinacao)
346
- maior_prazo = max(int(c.get('qtd_parcelas', 0)) for c in combinacao)
 
 
 
347
 
348
  valor_final = round(total_entrada + total_saldo, 2)
349
  credito_real = round(total_credito - total_entrada, 2)
@@ -357,7 +452,7 @@ def formatar_resultados(resultados):
357
  resposta += f"""
358
  --- COMBINAÇÃO {idx} ---
359
  🏢 Administradora: {adm}
360
- 🔢 Códigos: {', '.join(codigos)} ({len(combinacao)} cotas)
361
  💰 Crédito Total: {formatar_moeda(total_credito)}
362
  💵 Entrada (+comissão): {formatar_moeda(total_entrada)}
363
  📉 Saldo Devedor: {formatar_moeda(total_saldo)}
@@ -374,180 +469,191 @@ def formatar_resultados(resultados):
374
  return resposta
375
 
376
  # Carrega a planilha uma vez quando o aplicativo inicia
 
377
  carregar_planilha()
378
 
379
  def ajustar_e_buscar(credito_desejado, entrada_desejada, parcela_desejada, administradoras):
380
- return buscar_combinacoes_otimizado(
381
- credito_desejado, entrada_desejada, parcela_desejada, administradoras, max_combinacoes=15
382
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
383
 
384
  # Prepara a lista de administradoras para o Gradio
385
  def get_administradoras_options():
 
386
  if df is not None and planilha_carregada:
387
- adm_list = df[df['tipo_do_bem'].str.lower() == 'auto']['administradora'].unique()
388
- return sorted(adm_list)
389
- return []
390
-
391
- # Solução alternativa para importar Gradio
392
- try:
393
- import gradio as gr
394
- GRIDO_DISPONIVEL = True
395
- except ImportError as e:
396
- print(f"❌ Gradio não disponível: {e}")
397
- print("🔧 Usando interface alternativa...")
398
- GRIDO_DISPONIVEL = False
399
-
400
- # Cria uma interface alternativa simples se gradio não estiver disponível
401
- class SimpleInterface:
402
- def __init__(self):
403
- self.components = []
404
 
405
- def launch(self, **kwargs):
406
- print("Interface alternativa ativada")
407
- print("Para usar a interface completa, instale Gradio corretamente")
408
- # Simula a funcionalidade básica
409
- while True:
410
- print("\n" + "="*50)
411
- print("SIMULADOR DE CONSÓRCIO AUTO")
412
- print("="*50)
413
- try:
414
- credito = float(input("Crédito Desejado (R$): ") or 30000)
415
- entrada = float(input("Entrada Desejada (R$): ") or 3000)
416
- parcela = float(input("Parcela Desejada (R$): ") or 500)
417
-
418
- print("\nAdministradoras disponíveis:")
419
- adm_options = get_administradoras_options()
420
- for i, adm in enumerate(adm_options, 1):
421
- print(f"{i}. {adm}")
422
-
423
- print("\nDigite os números das administradoras separados por vírgula (ou deixe em branco para todas):")
424
- adm_input = input("Administradoras: ")
425
-
426
- if adm_input.strip():
427
- adm_indices = [int(x.strip()) for x in adm_input.split(",")]
428
- administradoras = [adm_options[i-1] for i in adm_indices if 1 <= i <= len(adm_options)]
429
- else:
430
- administradoras = adm_options
431
-
432
- print(f"\n🔍 Buscando combinações...")
433
- resultado = ajustar_e_buscar(credito, entrada, parcela, administradoras)
434
-
435
- print("\n" + "="*50)
436
- print("RESULTADOS:")
437
- print("="*50)
438
- print(resultado)
439
-
440
- continuar = input("\nDeseja fazer nova busca? (s/n): ")
441
- if continuar.lower() != 's':
442
- break
443
-
444
- except (ValueError, KeyboardInterrupt):
445
- print("\nOperação cancelada.")
446
- break
447
-
448
- if GRIDO_DISPONIVEL:
449
- # Criando a interface Gradio
450
- with gr.Blocks(title="Sistema de Busca de Consórcio Auto") as demo:
451
- gr.HTML("""
452
- <link href="https://fonts.googleapis.com/css2?family=Poppins&display=swap" rel="stylesheet">
453
- <style>
454
- * {
455
- font-family: 'Poppins', sans-serif !important;
456
- }
457
-
458
- body {
459
- background-color: #ff8f00 !important;
460
- color: black !important;
461
- }
462
-
463
- /* Inputs Numéricos: Crédito, Entrada, Parcela */
464
- input[type="number"] {
465
- background-color: white !important;
466
- border: 2px solid #000 !important;
467
- border-radius: 12px !important;
468
- color: black !important;
469
- padding: 10px !important;
470
- font-weight: 500;
471
- }
472
-
473
- /* Inputs de texto e áreas de texto (como a saída e código) */
474
- input[type="text"], textarea {
475
- background-color: white !important;
476
- border: 2px solid black !important;
477
- border-radius: 12px !important;
478
- color: black !important;
479
- padding: 10px !important;
480
- }
481
-
482
- input::placeholder, textarea::placeholder {
483
- color: #333 !important;
484
- opacity: 0.7;
485
- }
486
-
487
- .gr-checkbox label {
488
- display: flex;
489
- align-items: center;
490
- gap: 8px;
491
- font-weight: 600;
492
- }
493
-
494
- input[type="checkbox"] {
495
- accent-color: black !important;
496
- width: 18px;
497
- height: 18px;
498
- }
499
-
500
- .gr-button {
501
- font-weight: bold;
502
- border-radius: 10px !important;
503
- background-color: white !important;
504
- color: black !important;
505
- border: 2px solid black !important;
506
- }
507
-
508
- .gr-button:hover {
509
- background-color: #fff3e0 !important;
510
- color: black !important;
511
- }
512
-
513
- label {
514
- font-weight: 600 !important;
515
- }
516
- </style>
517
- """)
518
-
519
- gr.Markdown("# 🚗 Busca de Combinações de Consórcio Auto")
520
- gr.Markdown("Insira os valores desejados para crédito, entrada e parcela, e filtre por administradora.")
521
-
522
- with gr.Row():
523
- credito_input = gr.Number(label="Crédito Desejado (R$)", value=30000, step=1000)
524
- entrada_input = gr.Number(label="Entrada Desejada (R$)", value=3000, step=100)
525
- parcela_input = gr.Number(label="Parcela Desejada (R$)", value=500, step=10)
526
-
527
- administradoras_multiselect = gr.CheckboxGroup(
528
- label="Filtrar por Administradoras",
529
- choices=get_administradoras_options(),
530
- value=get_administradoras_options() # Seleciona todas por padrão
531
- )
532
 
533
- submit_btn = gr.Button("🔍 Buscar Combinações", variant="primary")
534
 
535
- output_text = gr.Textbox(label="Resultados da Busca", lines=20, max_lines=40, interactive=False, placeholder="Os resultados aparecerão aqui...")
 
 
 
 
 
 
536
 
537
- submit_btn.click(
538
- fn=ajustar_e_buscar,
539
- inputs=[credito_input, entrada_input, parcela_input, administradoras_multiselect],
540
- outputs=output_text
541
- )
 
542
 
543
- gr.Markdown("---")
544
- gr.Markdown("Desenvolvido para auxiliar na busca por cotas de consórcio ideais.")
545
-
546
- # Inicia o aplicativo Gradio
547
- if __name__ == "__main__":
548
- demo.launch(server_name="0.0.0.0", server_port=7860, share=False)
549
- else:
550
- # Usa interface alternativa
551
- if __name__ == "__main__":
552
- interface = SimpleInterface()
553
- interface.launch()
 
 
 
 
 
 
 
 
1
+ # 🔥 LIMITES DE SEGURANÇA - PARA EVITAR TIMEOUT NO HUGGING FACE
2
+ import os
3
+ os.environ["GRADIO_ANALYTICS_ENABLED"] = "False"
4
+ os.environ["GRADIO_QUEUE_ENABLED"] = "False"
5
+
6
+ MAX_COMBINATIONS = 10000 # Limite absoluto de combinações testadas
7
+ MAX_COTAS_POR_ADMIN = 100 # Limita cotas por administradora
8
+
9
+ # IMPORTS
10
  import pandas as pd
11
  import requests
12
  import io
13
  import itertools
14
  from collections import defaultdict
15
+ import gradio as gr
16
  import time
17
  import math
18
 
 
36
 
37
  try:
38
  print("⏳ Carregando planilha...")
39
+
40
+ # PRIMEIRO: Tenta carregar do arquivo local (Hugging Face)
41
+ try:
42
+ # Verifica se o arquivo existe localmente
43
+ if os.path.exists("cotas_consorcio.xlsx"):
44
+ print("📁 Encontrado arquivo local no Hugging Face")
45
+ df = pd.read_excel("cotas_consorcio.xlsx")
46
+ print(f"✅ Planilha carregada do arquivo local: {len(df)} registros")
47
+ else:
48
+ raise FileNotFoundError("Arquivo local não encontrado")
49
+
50
+ except Exception as e_local:
51
+ print(f"📁 Não foi possível carregar do arquivo local: {e_local}")
52
+ print("🌐 Tentando carregar do Google Drive...")
53
+
54
+ # SEGUNDO: Tenta carregar do Google Drive
55
+ response = requests.get(URL, timeout=30)
56
+ response.raise_for_status()
57
+ data = io.BytesIO(response.content)
58
+ df = pd.read_excel(data)
59
+ print(f"✅ Planilha carregada do Google Drive: {len(df)} registros")
60
+
61
+ # =========== PROCESSAMENTO DA PLANILHA ===========
62
+ print("🔧 Processando planilha...")
63
+
64
  # Padroniza nomes das colunas
65
  df.columns = df.columns.str.strip().str.lower().str.replace(" ", "_")
66
+ print("✅ Colunas padronizadas")
 
 
 
67
 
68
  # Converte colunas numéricas
69
  colunas_numericas = ['valor_credito', 'valor_entrada', 'valor_parcela', 'saldo_devedor', 'qtd_parcelas']
 
72
  df[coluna] = pd.to_numeric(df[coluna], errors='coerce').fillna(0)
73
 
74
  # Verifica colunas essenciais
75
+ colunas_essenciais = ['tipo_do_bem', 'administradora']
76
+ for col in colunas_essenciais:
77
+ if col not in df.columns:
78
+ print(f"⚠️ Coluna essencial '{col}' não encontrada")
79
 
80
  # Remove duplicatas
81
  df.drop_duplicates(inplace=True)
82
+ print(f"✅ Duplicatas removidas: {len(df)} registros únicos")
83
 
84
  # Pré-filtra e pré-processa dados de auto
85
+ if 'tipo_do_bem' in df.columns:
86
+ # Busca por variações do termo 'auto'
87
+ termos_auto = ['auto', 'automóvel', 'carro', 'veículo', 'automovel']
88
+ mascara_auto = df['tipo_do_bem'].astype(str).str.lower().isin([t.lower() for t in termos_auto])
89
+
90
+ if mascara_auto.any():
91
+ df_auto = df[mascara_auto].copy()
92
+ print(f"✅ Encontradas {len(df_auto)} cotas do tipo 'auto'")
93
+ else:
94
+ print("⚠️ Nenhuma cota do tipo 'auto' encontrada. Usando todos os registros.")
95
+ df_auto = df.copy()
96
+ else:
97
+ print("⚠️ Coluna 'tipo_do_bem' não encontrada. Usando todos os registros.")
98
+ df_auto = df.copy()
99
+
100
+ # Agrupa por administradora
101
  grupos_auto = agrupar_por_administradora(df_auto)
102
+
103
+ # 🔥 LIMITA COTAS POR ADMINISTRADORA PARA EVITAR TIMEOUT
104
+ for adm in list(grupos_auto.keys()):
105
+ grupos_auto[adm] = grupos_auto[adm][:MAX_COTAS_POR_ADMIN]
106
 
107
  planilha_carregada = True
108
  print("✅ Planilha carregada e pré-processada com sucesso")
 
112
 
113
  except Exception as e:
114
  print(f"❌ Erro ao carregar planilha: {str(e)}")
115
+ import traceback
116
+ traceback.print_exc()
117
  return False
118
 
119
  def agrupar_por_administradora(dataframe):
120
  """Agrupa cotas por administradora."""
121
  grupos = defaultdict(list)
122
  for _, row in dataframe.iterrows():
123
+ if 'administradora' in row and row['administradora']:
124
+ grupos[row['administradora']].append(row.to_dict())
125
  return grupos
126
 
127
  def calcular_tempo_parcelas(combinacao):
 
171
  print(f"❌ Erro ao calcular tempo de parcelas: {str(e)}")
172
  return ""
173
 
174
+ def buscar_combinacoes_otimizado(credito_desejado, entrada_desejada, parcela_desejada, administradoras=None, max_combinacoes=10):
175
  """Busca combinações de cotas dentro dos parâmetros especificados de forma otimizada."""
176
  global df_auto, grupos_auto
177
 
178
+ print(f"\n{'='*50}")
179
+ print(f"🔍 NOVA BUSCA - {time.strftime('%H:%M:%S')}")
180
+ print(f"💰 Crédito: R$ {credito_desejado:,.2f}")
181
+ print(f"💵 Entrada: R$ {entrada_desejada:,.2f}")
182
+ print(f"💲 Parcela: R$ {parcela_desejada:,.2f}")
183
+ print(f"🏢 Administradoras: {administradoras}")
184
+
185
+ # 🔥 LIMITE DE SEGURANÇA - EVITA TIMEOUT
186
+ if credito_desejado > 1000000: # 1 milhão
187
+ return "⚠️ Valor muito alto. Use valores até R$ 1.000.000 para evitar timeout."
188
+
189
  # Garante que o DataFrame está carregado
190
  if not planilha_carregada:
191
+ print("📥 Planilha não carregada. Tentando carregar...")
192
  if not carregar_planilha():
193
  return "❌ Erro: Planilha não carregada. Por favor, tente novamente."
194
 
195
  # Filtra administradoras se especificado
196
  if administradoras and len(administradoras) > 0:
197
  administradoras = [a.strip().lower() for a in administradoras]
198
+ grupos_filtrados = {adm: cotas for adm, cotas in grupos_auto.items() if adm.lower() in administradoras}
199
  print(f"🔍 Filtrando por administradoras: {administradoras}")
200
  else:
201
  grupos_filtrados = grupos_auto
 
211
  tolerancia_credito = 0.50 # 10%
212
  tolerancia_entrada = 0.50 # 10%
213
  tolerancia_parcela = 1.00 # 50%
214
+ max_cotas_combinacao = 10 # Até 10 cotas por combinação (REDUZIDO)
215
  elif credito_desejado > 100000: # Para valores acima de 100 Mil
216
  tolerancia_credito = 0.25 # 10%
217
  tolerancia_entrada = 0.25 # 10%
218
  tolerancia_parcela = 0.50 # 30%
219
+ max_cotas_combinacao = 8 # Até 8 cotas por combinação (REDUZIDO)
220
  else: # Para valores até 100k
221
  tolerancia_credito = 0.15 # 5%
222
  tolerancia_entrada = 0.15 # 5%
223
  tolerancia_parcela = 0.30 # 25%
224
+ max_cotas_combinacao = 6 # Até 6 cotas por combinação (REDUZIDO)
225
 
226
  # Define faixas de tolerância
227
  credito_min = credito_desejado * (1 - tolerancia_credito)
 
231
  parcela_min = parcela_desejada * (1 - tolerancia_parcela)
232
  parcela_max = parcela_desejada * (1 + tolerancia_parcela)
233
 
 
234
  print(f"🔧 Tolerâncias: Crédito: ±{tolerancia_credito*100:.1f}%, Entrada: ±{tolerancia_entrada*100:.1f}%, Parcela: ±{tolerancia_parcela*100:.1f}%")
 
 
 
235
  print(f"🔧 Máximo de cotas por combinação: {max_cotas_combinacao}")
236
 
237
  resultados = []
 
248
  for r in range(1, min(max_cotas_combinacao + 1, len(cotas_para_combinar) + 1)):
249
  # Limita o número de combinações testadas por administradora
250
  if credito_desejado > 500000:
251
+ max_combinacoes_por_adm = 1000 # REDUZIDO
252
  else:
253
+ max_combinacoes_por_adm = 500 # REDUZIDO
254
+
255
  combinacoes_geradas = 0
256
 
257
  for combinacao in itertools.combinations(cotas_para_combinar, r):
 
261
  total_combinacoes_testadas += 1
262
  combinacoes_geradas += 1
263
 
264
+ # 🔥 LIMITE GLOBAL DE COMBINAÇÕES TESTADAS
265
+ if total_combinacoes_testadas > MAX_COMBINATIONS:
266
+ print(f"⚠️ Limite máximo de combinações atingido: {MAX_COMBINATIONS}")
267
+ break
268
+
269
+ total_credito = sum(c.get('valor_credito', 0) for c in combinacao)
270
+ total_entrada_base = sum(c.get('valor_entrada', 0) for c in combinacao)
271
  total_entrada = total_entrada_base + (total_credito * 0.085)
272
+ total_parcelas = sum(c.get('valor_parcela', 0) for c in combinacao)
273
 
274
  # Verifica se a combinação está dentro das tolerâncias
275
  if (credito_min <= total_credito <= credito_max and
276
  entrada_min <= total_entrada <= entrada_max and
277
  parcela_min <= total_parcelas <= parcela_max):
278
 
279
+ total_saldo = sum(c.get('saldo_devedor', 0) for c in combinacao)
280
  valor_final = round(total_entrada + total_saldo, 2)
281
  diferenca = abs(valor_final - total_credito)
282
 
 
286
  if len(resultados) >= max_combinacoes * 3:
287
  break
288
 
289
+ if total_combinacoes_testadas > MAX_COMBINATIONS:
290
+ break
291
+
292
  if len(resultados) >= max_combinacoes * 3:
293
  break
294
 
295
+ if total_combinacoes_testadas > MAX_COMBINATIONS:
296
+ break
297
+
298
  if len(resultados) >= max_combinacoes * 3:
299
  break
300
 
 
310
  resultados.sort(key=lambda x: x[2])
311
  return formatar_resultados(resultados[:max_combinacoes])
312
 
313
+ def buscar_combinacoes_amplo(credito_desejado, entrada_desejada, parcela_desejada, administradoras=None, max_combinacoes=5):
314
  """Busca combinações com tolerâncias mais amplas para valores altos."""
315
  global grupos_auto
316
 
317
+ print(f"🔍 BUSCA AMPLA - {time.strftime('%H:%M:%S')}")
318
+
319
+ # 🔥 LIMITE DE SEGURANÇA - EVITA TIMEOUT
320
+ if credito_desejado > 1000000:
321
+ return "⚠️ Busca ampla desativada para valores acima de R$ 1.000.000"
322
+
323
  # Filtra administradoras se especificado
324
  if administradoras and len(administradoras) > 0:
325
  administradoras = [a.strip().lower() for a in administradoras]
326
+ grupos_filtrados = {adm: cotas for adm, cotas in grupos_auto.items() if adm.lower() in administradoras}
327
  else:
328
  grupos_filtrados = grupos_auto
329
 
 
332
  tolerancia_credito = 0.50 # 15%
333
  tolerancia_entrada = 0.50 # 15%
334
  tolerancia_parcela = 1.00 # 75%
335
+ max_cotas_combinacao = 8 # REDUZIDO
336
  elif credito_desejado > 100000:
337
  tolerancia_credito = 0.25 # 10%
338
  tolerancia_entrada = 0.25 # 10%
339
  tolerancia_parcela = 0.50 # 50%
340
+ max_cotas_combinacao = 6 # REDUZIDO
341
  else:
342
  tolerancia_credito = 0.15 # 5%
343
  tolerancia_entrada = 0.15 # 5%
344
  tolerancia_parcela = 0.30 # 35%
345
+ max_cotas_combinacao = 4 # REDUZIDO
346
 
347
  credito_min = credito_desejado * (1 - tolerancia_credito)
348
  credito_max = credito_desejado * (1 + tolerancia_credito)
 
363
  # Gera combinações
364
  for r in range(1, min(max_cotas_combinacao + 1, len(cotas) + 1)):
365
  combinacoes_geradas = 0
366
+ max_combinacoes_por_adm = 300 # REDUZIDO
367
 
368
  for combinacao in itertools.combinations(cotas, r):
369
  if combinacoes_geradas >= max_combinacoes_por_adm:
 
372
  total_combinacoes_testadas += 1
373
  combinacoes_geradas += 1
374
 
375
+ # 🔥 LIMITE GLOBAL DE COMBINAÇÕES TESTADAS
376
+ if total_combinacoes_testadas > MAX_COMBINATIONS:
377
+ print(f"⚠️ Limite máximo de combinações atingido: {MAX_COMBINATIONS}")
378
+ break
379
+
380
+ total_credito = sum(c.get('valor_credito', 0) for c in combinacao)
381
+ total_entrada_base = sum(c.get('valor_entrada', 0) for c in combinacao)
382
  total_entrada = total_entrada_base + (total_credito * 0.085)
383
+ total_parcelas = sum(c.get('valor_parcela', 0) for c in combinacao)
384
 
385
  if (credito_min <= total_credito <= credito_max and
386
  entrada_min <= total_entrada <= entrada_max and
387
  parcela_min <= total_parcelas <= parcela_max):
388
 
389
+ total_saldo = sum(c.get('saldo_devedor', 0) for c in combinacao)
390
  valor_final = round(total_entrada + total_saldo, 2)
391
  diferenca = abs(valor_final - total_credito)
392
 
 
395
  if len(resultados) >= max_combinacoes * 2:
396
  break
397
 
398
+ if total_combinacoes_testadas > MAX_COMBINATIONS:
399
+ break
400
+
401
  if len(resultados) >= max_combinacoes * 2:
402
  break
403
 
404
+ if total_combinacoes_testadas > MAX_COMBINATIONS:
405
+ break
406
+
407
  if len(resultados) >= max_combinacoes * 2:
408
  break
409
 
 
417
  return formatar_resultados(resultados[:max_combinacoes])
418
 
419
  def formatar_moeda(valor):
420
+ try:
421
+ return f"R$ {float(valor):,.2f}".replace(",", "v").replace(".", ",").replace("v", ".")
422
+ except:
423
+ return f"R$ {valor}"
424
 
425
  def formatar_resultados(resultados):
426
  """Formata os resultados para exibição INCLUINDO TEMPO DE PARCELAS CORRETO."""
 
429
 
430
  resposta = ""
431
  for idx, (adm, combinacao, _) in enumerate(resultados, 1):
432
+ codigos = [str(c.get('codigo', 'N/A')) for c in combinacao if c.get('codigo')]
433
+ total_credito = sum(c.get('valor_credito', 0) for c in combinacao)
434
+ total_entrada_base = sum(c.get('valor_entrada', 0) for c in combinacao)
435
  total_entrada = total_entrada_base + (total_credito * 0.085)
436
+ total_saldo = sum(c.get('saldo_devedor', 0) for c in combinacao)
437
+ total_parcelas = sum(c.get('valor_parcela', 0) for c in combinacao)
438
+
439
+ # Encontra o maior prazo
440
+ prazos = [int(c.get('qtd_parcelas', 0)) for c in combinacao if c.get('qtd_parcelas')]
441
+ maior_prazo = max(prazos) if prazos else 0
442
 
443
  valor_final = round(total_entrada + total_saldo, 2)
444
  credito_real = round(total_credito - total_entrada, 2)
 
452
  resposta += f"""
453
  --- COMBINAÇÃO {idx} ---
454
  🏢 Administradora: {adm}
455
+ 🔢 Códigos: {', '.join(codigos) if codigos else 'N/A'} ({len(combinacao)} cotas)
456
  💰 Crédito Total: {formatar_moeda(total_credito)}
457
  💵 Entrada (+comissão): {formatar_moeda(total_entrada)}
458
  📉 Saldo Devedor: {formatar_moeda(total_saldo)}
 
469
  return resposta
470
 
471
  # Carrega a planilha uma vez quando o aplicativo inicia
472
+ print("🚀 Iniciando sistema de busca de consórcio...")
473
  carregar_planilha()
474
 
475
  def ajustar_e_buscar(credito_desejado, entrada_desejada, parcela_desejada, administradoras):
476
+ print(f"\n📥 API CHAMADA - ajustar_e_buscar")
477
+ print(f" Parâmetros: {credito_desejado}, {entrada_desejada}, {parcela_desejada}, {administradoras}")
478
+
479
+ try:
480
+ resultado = buscar_combinacoes_otimizado(
481
+ float(credito_desejado),
482
+ float(entrada_desejada),
483
+ float(parcela_desejada),
484
+ administradoras,
485
+ max_combinacoes=10
486
+ )
487
+ print(f"✅ Resultado gerado com sucesso")
488
+ return resultado
489
+ except Exception as e:
490
+ print(f"❌ Erro em ajustar_e_buscar: {str(e)}")
491
+ import traceback
492
+ traceback.print_exc()
493
+ return f"❌ Erro interno do servidor: {str(e)}"
494
 
495
  # Prepara a lista de administradoras para o Gradio
496
  def get_administradoras_options():
497
+ """RETORNA AS ADMINISTRADORAS EM FORMATO ORIGINAL (COM LETRAS MAIÚSCULAS)"""
498
  if df is not None and planilha_carregada:
499
+ try:
500
+ # Busca por variações do termo 'auto'
501
+ termos_auto = ['auto', 'automóvel', 'carro', 'veículo', 'automovel']
502
+ mascara_auto = df['tipo_do_bem'].astype(str).str.lower().isin([t.lower() for t in termos_auto])
 
 
 
 
 
 
 
 
 
 
 
 
 
503
 
504
+ if mascara_auto.any():
505
+ df_filtrado = df[mascara_auto]
506
+ else:
507
+ df_filtrado = df
508
+
509
+ if 'administradora' in df_filtrado.columns:
510
+ # Extrai as administradoras e mantém o formato ORIGINAL
511
+ adm_list = df_filtrado['administradora'].dropna().unique()
512
+ adm_list = [str(adm).strip() for adm in adm_list if str(adm).strip()]
513
+
514
+ # Remove duplicatas (considerando case insensitive)
515
+ unique_adms = []
516
+ seen = set()
517
+ for adm in adm_list:
518
+ adm_lower = adm.lower()
519
+ if adm_lower not in seen:
520
+ seen.add(adm_lower)
521
+ unique_adms.append(adm)
522
+
523
+ # Ordena alfabeticamente
524
+ unique_adms.sort()
525
+ print(f"✅ Administradoras carregadas: {len(unique_adms)}")
526
+ return unique_adms
527
+
528
+ except Exception as e:
529
+ print(f"❌ Erro ao carregar administradoras: {str(e)}")
530
+
531
+ # Fallback se algo der errado
532
+ fallback = ["Ademicon", "Banco do Brasil", "Bancorbrás", "Banrisul", "Bradesco",
533
+ "Caixa", "Canopus", "Embracon", "HS", "Itaú", "Santander"]
534
+ print(f"⚠️ Usando lista fallback: {fallback}")
535
+ return fallback
536
+
537
+ # Criando a interface Gradio COM AS ADMINISTRADORAS CORRETAS
538
+ with gr.Blocks(title="Sistema de Busca de Consórcio Auto") as demo:
539
+ gr.HTML("""
540
+ <link href="https://fonts.googleapis.com/css2?family=Poppins&display=swap" rel="stylesheet">
541
+ <style>
542
+ * {
543
+ font-family: 'Poppins', sans-serif !important;
544
+ }
545
+
546
+ body {
547
+ background-color: #ff8f00 !important;
548
+ color: black !important;
549
+ }
550
+
551
+ /* Inputs Numéricos: Crédito, Entrada, Parcela */
552
+ input[type="number"] {
553
+ background-color: white !important;
554
+ border: 2px solid #000 !important;
555
+ border-radius: 12px !important;
556
+ color: black !important;
557
+ padding: 10px !important;
558
+ font-weight: 500;
559
+ }
560
+
561
+ /* Inputs de texto e áreas de texto (como a saída e código) */
562
+ input[type="text"], textarea {
563
+ background-color: white !important;
564
+ border: 2px solid black !important;
565
+ border-radius: 12px !important;
566
+ color: black !important;
567
+ padding: 10px !important;
568
+ }
569
+
570
+ input::placeholder, textarea::placeholder {
571
+ color: #333 !important;
572
+ opacity: 0.7;
573
+ }
574
+
575
+ .gr-checkbox label {
576
+ display: flex;
577
+ align-items: center;
578
+ gap: 8px;
579
+ font-weight: 600;
580
+ }
581
+
582
+ input[type="checkbox"] {
583
+ accent-color: black !important;
584
+ width: 18px;
585
+ height: 18px;
586
+ }
587
+
588
+ .gr-button {
589
+ font-weight: bold;
590
+ border-radius: 10px !important;
591
+ background-color: white !important;
592
+ color: black !important;
593
+ border: 2px solid black !important;
594
+ }
595
+
596
+ .gr-button:hover {
597
+ background-color: #fff3e0 !important;
598
+ color: black !important;
599
+ }
600
+
601
+ label {
602
+ font-weight: 600 !important;
603
+ }
604
+ </style>
605
+ """)
606
+
607
+ gr.Markdown("# 🚗 Busca de Combinações de Consórcio Auto")
608
+ gr.Markdown("Insira os valores desejados para crédito, entrada e parcela, e filtre por administradora.")
609
+
610
+ with gr.Row():
611
+ credito_input = gr.Number(label="Crédito Desejado (R$)", value=30000, step=1000)
612
+ entrada_input = gr.Number(label="Entrada Desejada (R$)", value=3000, step=100)
613
+ parcela_input = gr.Number(label="Parcela Desejada (R$)", value=500, step=10)
614
+
615
+ # Carrega as opções de administradoras
616
+ admin_opcoes = get_administradoras_options()
617
+
618
+ administradoras_multiselect = gr.CheckboxGroup(
619
+ label="Filtrar por Administradoras",
620
+ choices=admin_opcoes,
621
+ value=admin_opcoes[:3] if len(admin_opcoes) >= 3 else admin_opcoes,
622
+ info=f"Total de {len(admin_opcoes)} administradoras disponíveis"
623
+ )
 
 
 
 
 
 
 
624
 
625
+ submit_btn = gr.Button("🔍 Buscar Combinações", variant="primary")
626
 
627
+ output_text = gr.Textbox(
628
+ label="Resultados da Busca",
629
+ lines=20,
630
+ max_lines=40,
631
+ interactive=False,
632
+ placeholder="Os resultados aparecerão aqui..."
633
+ )
634
 
635
+ submit_btn.click(
636
+ fn=ajustar_e_buscar,
637
+ inputs=[credito_input, entrada_input, parcela_input, administradoras_multiselect],
638
+ outputs=output_text,
639
+ api_name="ajustar_e_buscar"
640
+ )
641
 
642
+ # Adiciona informação sobre as administradoras
643
+ gr.Markdown(f"""
644
+ ### 📋 Administradoras disponíveis:
645
+ {', '.join(admin_opcoes)}
646
+
647
+ ---
648
+ **Desenvolvido para auxiliar na busca por cotas de consórcio ideais.**
649
+ """)
650
+
651
+ # Inicia o aplicativo Gradio
652
+ if __name__ == "__main__":
653
+ print("🌐 Iniciando servidor Gradio...")
654
+ demo.launch(
655
+ server_name="0.0.0.0",
656
+ server_port=7860,
657
+ debug=True,
658
+ show_error=True
659
+ )