Arthur-Attili commited on
Commit
5ec82e7
·
verified ·
1 Parent(s): 70d357a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +104 -396
app.py CHANGED
@@ -1,110 +1,75 @@
 
 
 
1
  import pandas as pd
2
  import requests
3
  import io
4
  import itertools
5
  from collections import defaultdict
6
  import gradio as gr
7
- import time
8
- import math
9
 
10
- # 🔧 CONFIGURAÇÕES GLOBAIS
11
  FILE_ID = "1EoH8V3Fyls0mNa95VcBN6ZL1B552ty3X"
12
  URL = f"https://drive.google.com/uc?export=download&id={FILE_ID}"
13
 
14
- # Variáveis globais para armazenamento
15
- df = None
16
  df_auto = None
17
  grupos_auto = None
18
  planilha_carregada = False
 
19
 
20
  def carregar_planilha():
21
- """Carrega e prepara a planilha para análise."""
22
- global df, df_auto, grupos_auto, planilha_carregada
23
-
24
- # Se já carregou, não precisa carregar novamente
25
  if planilha_carregada:
26
  return True
27
-
28
  try:
29
  print("⏳ Carregando planilha...")
30
  response = requests.get(URL)
31
  response.raise_for_status()
32
  data = io.BytesIO(response.content)
33
  df = pd.read_excel(data)
34
-
35
- # Padroniza nomes das colunas
36
  df.columns = df.columns.str.strip().str.lower().str.replace(" ", "_")
37
-
38
- # Padroniza os nomes das administradoras
39
- if 'administradora' in df.columns:
40
- df['administradora'] = df['administradora'].astype(str).str.strip().str.lower()
41
-
42
- # Converte colunas numéricas
43
  colunas_numericas = ['valor_credito', 'valor_entrada', 'valor_parcela', 'saldo_devedor', 'qtd_parcelas']
44
- for coluna in colunas_numericas:
45
- if coluna in df.columns:
46
- df[coluna] = pd.to_numeric(df[coluna], errors='coerce').fillna(0)
47
-
48
- # Verifica colunas essenciais
49
  if 'tipo_do_bem' not in df.columns or 'administradora' not in df.columns:
50
  raise ValueError("Colunas essenciais não encontradas")
51
-
52
- # Remove duplicatas
53
  df.drop_duplicates(inplace=True)
54
-
55
- # Pré-filtra e pré-processa dados de auto
56
  df_auto = df[df['tipo_do_bem'].str.lower() == 'auto'].copy()
57
- grupos_auto = agrupar_por_administradora(df_auto)
58
-
 
 
 
 
59
  planilha_carregada = True
60
- print("✅ Planilha carregada e pré-processada com sucesso")
61
- print(f"✅ Total de cotas de auto: {len(df_auto)}")
62
- print(f"✅ Administradoras disponíveis: {list(grupos_auto.keys())}")
63
  return True
64
-
65
  except Exception as e:
66
- print(f"❌ Erro ao carregar planilha: {str(e)}")
67
  return False
68
 
69
- def agrupar_por_administradora(dataframe):
70
- """Agrupa cotas por administradora."""
71
- grupos = defaultdict(list)
72
- for _, row in dataframe.iterrows():
73
- grupos[row['administradora']].append(row.to_dict())
74
- return grupos
75
 
76
  def calcular_tempo_parcelas(combinacao):
77
- """Calcula o tempo de parcelas baseado nas cotas da combinação - VERSÃO CORRIGIDA."""
78
  if not combinacao:
79
  return ""
80
-
81
  try:
82
- # Ordena as cotas pelo prazo (menor prazo primeiro)
83
  cotas_ordenadas = sorted(combinacao, key=lambda x: int(x.get('qtd_parcelas', 0)))
84
-
85
- # Encontra todos os prazos únicos e ordena
86
  prazos = sorted(set(int(c.get('qtd_parcelas', 0)) for c in combinacao))
87
-
88
  if not prazos or prazos[0] <= 0:
89
  return ""
90
-
91
  resultado = []
92
  prazo_anterior = 0
93
-
94
- for i, prazo_atual in enumerate(prazos):
95
- # Calcula a soma das parcelas das cotas que estão ativas neste intervalo
96
  cotas_ativas = [c for c in cotas_ordenadas if int(c.get('qtd_parcelas', 0)) >= prazo_anterior + 1]
97
  valor_parcela_total = sum(c.get('valor_parcela', 0) for c in cotas_ativas)
98
-
99
  inicio = prazo_anterior + 1
100
  fim = prazo_atual
101
-
102
  if inicio <= fim and valor_parcela_total > 0:
103
  resultado.append(f"{inicio}x à {fim}x: {formatar_moeda(valor_parcela_total)}/mês")
104
-
105
  prazo_anterior = prazo_atual
106
-
107
- # Adiciona faixa final se houver cotas com prazos além do último ponto de quebra
108
  if prazos and prazo_anterior < max(prazos):
109
  cotas_ativas_finais = [c for c in cotas_ordenadas if int(c.get('qtd_parcelas', 0)) > prazo_anterior]
110
  if cotas_ativas_finais:
@@ -113,381 +78,124 @@ def calcular_tempo_parcelas(combinacao):
113
  fim_final = max(prazos)
114
  if valor_parcela_final > 0:
115
  resultado.append(f"{inicio_final}x à {fim_final}x: {formatar_moeda(valor_parcela_final)}/mês")
116
-
117
  return "\n".join(resultado) if resultado else ""
118
-
119
  except Exception as e:
120
- print(f"Erro ao calcular tempo de parcelas: {str(e)}")
121
  return ""
122
 
123
- def buscar_combinacoes_otimizado(credito_desejado, entrada_desejada, parcela_desejada, administradoras=None, max_combinacoes=15):
124
- """Busca combinações de cotas dentro dos parâmetros especificados de forma otimizada."""
125
- global df_auto, grupos_auto
126
-
127
- # Garante que o DataFrame está carregado
128
  if not planilha_carregada:
129
  if not carregar_planilha():
130
- return "❌ Erro: Planilha não carregada. Por favor, tente novamente."
131
-
132
- # Filtra administradoras se especificado
133
- if administradoras and len(administradoras) > 0:
134
- administradoras = [a.strip().lower() for a in administradoras]
135
- grupos_filtrados = {adm: cotas for adm, cotas in grupos_auto.items() if adm in administradoras}
136
- print(f"🔍 Filtrando por administradoras: {administradoras}")
137
  else:
138
- grupos_filtrados = grupos_auto
139
- print("🔍 Buscando em todas as administradoras")
140
-
141
- if not grupos_filtrados:
142
- return "🚫 Nenhuma cota encontrada com os filtros aplicados. Tente ajustar os parâmetros."
143
-
144
- print(f"🔍 Total de administradoras no filtro: {len(grupos_filtrados)}")
145
-
146
- # TOLERÂNCIAS - Ajustadas para valores de auto (geralmente menores que imóveis)
147
- if credito_desejado > 5000000: # Para valores acima de 5 Milhões
148
- tolerancia_credito = 0.50 # 10%
149
- tolerancia_entrada = 0.50 # 10%
150
- tolerancia_parcela = 1.00 # 50%
151
- max_cotas_combinacao = 25 # Até 25 cotas por combinação
152
- elif credito_desejado > 100000: # Para valores acima de 100 Mil
153
- tolerancia_credito = 0.25 # 10%
154
- tolerancia_entrada = 0.25 # 10%
155
- tolerancia_parcela = 0.50 # 30%
156
- max_cotas_combinacao = 15 # Até 15 cotas por combinação
157
- else: # Para valores até 100k
158
- tolerancia_credito = 0.15 # 5%
159
- tolerancia_entrada = 0.15 # 5%
160
- tolerancia_parcela = 0.30 # 25%
161
- max_cotas_combinacao = 15 # Até 15 cotas por combinação
162
-
163
- # Define faixas de tolerância
164
- credito_min = credito_desejado * (1 - tolerancia_credito)
165
- credito_max = credito_desejado * (1 + tolerancia_credito)
166
- entrada_min = entrada_desejada * (1 - tolerancia_entrada)
167
- entrada_max = entrada_desejada * (1 + tolerancia_entrada)
168
- parcela_min = parcela_desejada * (1 - tolerancia_parcela)
169
- parcela_max = parcela_desejada * (1 + tolerancia_parcela)
170
-
171
- print(f"💰 Valores desejados: Crédito: R$ {credito_desejado:,.2f}, Entrada: R$ {entrada_desejada:,.2f}, Parcela: R$ {parcela_desejada:,.2f}")
172
- print(f"🔧 Tolerâncias: Crédito: ±{tolerancia_credito*100:.1f}%, Entrada: ±{tolerancia_entrada*100:.1f}%, Parcela: ±{tolerancia_parcela*100:.1f}%")
173
- print(f"🔧 Faixas aceitáveis: Crédito: R$ {credito_min:,.2f} - R$ {credito_max:,.2f}")
174
- print(f"🔧 Faixas aceitáveis: Entrada: R$ {entrada_min:,.2f} - R$ {entrada_max:,.2f}")
175
- print(f"🔧 Faixas aceitáveis: Parcela: R$ {parcela_min:,.2f} - R$ {parcela_max:,.2f}")
176
- print(f"🔧 Máximo de cotas por combinação: {max_cotas_combinacao}")
177
-
178
- resultados = []
179
- total_combinacoes_testadas = 0
180
-
181
- # Para cada administradora, busca combinações
182
- for adm, cotas in grupos_filtrados.items():
183
- print(f"🔍 Analisando administradora: {adm} ({len(cotas)} cotas)")
184
-
185
- # Para valores altos, NÃO filtramos cotas individuais primeiro
186
- cotas_para_combinar = cotas
187
-
188
- # Gera combinações
189
- for r in range(1, min(max_cotas_combinacao + 1, len(cotas_para_combinar) + 1)):
190
- # Limita o número de combinações testadas por administradora
191
- if credito_desejado > 500000:
192
- max_combinacoes_por_adm = 5000
193
- else:
194
- max_combinacoes_por_adm = 2000
195
- combinacoes_geradas = 0
196
-
197
- for combinacao in itertools.combinations(cotas_para_combinar, r):
198
- if combinacoes_geradas >= max_combinacoes_por_adm:
199
- break
200
-
201
- total_combinacoes_testadas += 1
202
- combinacoes_geradas += 1
203
-
204
- total_credito = sum(c['valor_credito'] for c in combinacao)
205
- total_entrada_base = sum(c['valor_entrada'] for c in combinacao)
206
- total_entrada = total_entrada_base + (total_credito * 0.085)
207
- total_parcelas = sum(c['valor_parcela'] for c in combinacao)
208
-
209
- # Verifica se a combinação está dentro das tolerâncias
210
- if (credito_min <= total_credito <= credito_max and
211
- entrada_min <= total_entrada <= entrada_max and
212
- parcela_min <= total_parcelas <= parcela_max):
213
-
214
- total_saldo = sum(c['saldo_devedor'] for c in combinacao)
215
- valor_final = round(total_entrada + total_saldo, 2)
216
- diferenca = abs(valor_final - total_credito)
217
-
218
- resultados.append((adm, combinacao, diferenca))
219
-
220
- # Para se já encontrou combinações suficientes
221
- if len(resultados) >= max_combinacoes * 3:
222
- break
223
-
224
- if len(resultados) >= max_combinacoes * 3:
225
- break
226
-
227
- if len(resultados) >= max_combinacoes * 3:
228
- break
229
-
230
- print(f"🔍 Total de combinações testadas: {total_combinacoes_testadas}")
231
- print(f"🔍 Combinações válidas encontradas: {len(resultados)}")
232
-
233
- if not resultados:
234
- # Tenta uma busca mais ampla se não encontrou resultados
235
- print("🔍 Nenhuma combinação encontrada. Tentando busca ampla...")
236
- return buscar_combinacoes_amplo(credito_desejado, entrada_desejada, parcela_desejada, administradoras, max_combinacoes)
237
-
238
- # Ordena pela diferença e pega as melhores
239
- resultados.sort(key=lambda x: x[2])
240
- return formatar_resultados(resultados[:max_combinacoes])
241
-
242
- def buscar_combinacoes_amplo(credito_desejado, entrada_desejada, parcela_desejada, administradoras=None, max_combinacoes=15):
243
- """Busca combinações com tolerâncias mais amplas para valores altos."""
244
- global grupos_auto
245
-
246
- # Filtra administradoras se especificado
247
- if administradoras and len(administradoras) > 0:
248
- administradoras = [a.strip().lower() for a in administradoras]
249
- grupos_filtrados = {adm: cotas for adm, cotas in grupos_auto.items() if adm in administradoras}
250
- else:
251
- grupos_filtrados = grupos_auto
252
-
253
- # Tolerâncias mais amplas para valores altos
254
- if credito_desejado > 5000000:
255
- tolerancia_credito = 0.50 # 15%
256
- tolerancia_entrada = 0.50 # 15%
257
- tolerancia_parcela = 1.00 # 75%
258
- max_cotas_combinacao = 25 # Até 10 cotas por combinação
259
- elif credito_desejado > 100000:
260
- tolerancia_credito = 0.25 # 10%
261
- tolerancia_entrada = 0.25 # 10%
262
- tolerancia_parcela = 0.50 # 50%
263
- max_cotas_combinacao = 15 # Até 8 cotas por combinação
264
  else:
265
- tolerancia_credito = 0.15 # 5%
266
- tolerancia_entrada = 0.15 # 5%
267
- tolerancia_parcela = 0.30 # 35%
268
- max_cotas_combinacao = 15 # Até 6 cotas por combinação
269
 
270
- credito_min = credito_desejado * (1 - tolerancia_credito)
271
- credito_max = credito_desejado * (1 + tolerancia_credito)
272
- entrada_min = entrada_desejada * (1 - tolerancia_entrada)
273
- entrada_max = entrada_desejada * (1 + tolerancia_entrada)
274
- parcela_min = parcela_desejada * (1 - tolerancia_parcela)
275
- parcela_max = parcela_desejada * (1 + tolerancia_parcela)
276
-
277
- print(f"🔍 BUSCA AMPLA: Tolerâncias: Crédito: ±{tolerancia_credito*100:.1f}%, Entrada: ±{tolerancia_entrada*100:.1f}%, Parcela: ±{tolerancia_parcela*100:.1f}%")
278
- print(f"🔍 BUSCA AMPLA: Máximo de cotas por combinação: {max_cotas_combinacao}")
279
 
280
  resultados = []
281
- total_combinacoes_testadas = 0
282
-
283
- for adm, cotas in grupos_filtrados.items():
284
- print(f"🔍 BUSCA AMPLA - Analisando administradora: {adm}")
285
-
286
- # Gera combinações
287
- for r in range(1, min(max_cotas_combinacao + 1, len(cotas) + 1)):
288
- combinacoes_geradas = 0
289
- max_combinacoes_por_adm = 5000
290
-
291
- for combinacao in itertools.combinations(cotas, r):
292
- if combinacoes_geradas >= max_combinacoes_por_adm:
293
- break
294
-
295
- total_combinacoes_testadas += 1
296
- combinacoes_geradas += 1
297
-
298
- total_credito = sum(c['valor_credito'] for c in combinacao)
299
- total_entrada_base = sum(c['valor_entrada'] for c in combinacao)
300
- total_entrada = total_entrada_base + (total_credito * 0.085)
301
- total_parcelas = sum(c['valor_parcela'] for c in combinacao)
302
-
303
- if (credito_min <= total_credito <= credito_max and
304
- entrada_min <= total_entrada <= entrada_max and
305
- parcela_min <= total_parcelas <= parcela_max):
306
-
307
- total_saldo = sum(c['saldo_devedor'] for c in combinacao)
308
- valor_final = round(total_entrada + total_saldo, 2)
309
- diferenca = abs(valor_final - total_credito)
310
-
311
- resultados.append((adm, combinacao, diferenca))
312
-
313
- if len(resultados) >= max_combinacoes * 2:
314
  break
315
-
316
- if len(resultados) >= max_combinacoes * 2:
317
  break
318
-
319
- if len(resultados) >= max_combinacoes * 2:
320
  break
321
 
322
- print(f"🔍 BUSCA AMPLA: Total de combinações testadas: {total_combinacoes_testadas}")
323
- print(f"🔍 BUSCA AMPLA: Combinações válidas encontradas: {len(resultados)}")
324
-
325
  if not resultados:
326
- return "🚫 Nenhuma combinação adequada encontrada com os parâmetros fornecidos."
327
-
328
  resultados.sort(key=lambda x: x[2])
329
- return formatar_resultados(resultados[:max_combinacoes])
330
-
331
- def formatar_moeda(valor):
332
- return f"R$ {valor:,.2f}".replace(",", "v").replace(".", ",").replace("v", ".")
333
 
334
  def formatar_resultados(resultados):
335
- """Formata os resultados para exibição INCLUINDO TEMPO DE PARCELAS CORRETO."""
336
  if not resultados:
337
- return "🚫 Nenhuma combinação adequada encontrada com os parâmetros fornecidos."
338
-
339
  resposta = ""
340
- for idx, (adm, combinacao, _) in enumerate(resultados, 1):
341
- codigos = [str(c.get('codigo', 'N/A')) for c in combinacao]
342
- total_credito = sum(c['valor_credito'] for c in combinacao)
343
- total_entrada_base = sum(c['valor_entrada'] for c in combinacao)
344
- total_entrada = total_entrada_base + (total_credito * 0.085)
345
- total_saldo = sum(c['saldo_devedor'] for c in combinacao)
346
- total_parcelas = sum(c['valor_parcela'] for c in combinacao)
347
- maior_prazo = max(int(c.get('qtd_parcelas', 0)) for c in combinacao)
348
-
349
- valor_final = round(total_entrada + total_saldo, 2)
350
- credito_real = round(total_credito - total_entrada, 2)
351
- taxa_total = total_saldo - credito_real
352
- taxa_percentual = (taxa_total / credito_real) * 100 if credito_real > 0 else 0
353
- taxa_mensal = taxa_percentual / maior_prazo if maior_prazo > 0 else 0
354
-
355
- # CHAMADA CORRIGIDA para calcular tempo de parcelas
356
- tempo_parcelas = calcular_tempo_parcelas(combinacao)
357
-
358
  resposta += f"""
359
  --- COMBINAÇÃO {idx} ---
360
  🏢 Administradora: {adm}
361
- 🔢 Códigos: {', '.join(codigos)} ({len(combinacao)} cotas)
362
- 💰 Crédito Total: {formatar_moeda(total_credito)}
363
- 💵 Entrada (+comissão): {formatar_moeda(total_entrada)}
364
  📉 Saldo Devedor: {formatar_moeda(total_saldo)}
365
- 💲 Parcela: {formatar_moeda(total_parcelas)}
366
  ⏳ Prazo: {maior_prazo} meses
367
  🔚 Valor Final: {formatar_moeda(valor_final)}
368
- 💠 Crédito Líquido: {formatar_moeda(credito_real)}
369
- 📊 Taxa Total: {taxa_percentual:.2f}% ({formatar_moeda(taxa_total)})
370
  🗓️ Taxa Mensal: {taxa_mensal:.2f}%
371
  🗓️ Taxa Anual: {taxa_mensal * 12:.2f}%
372
  ⏰ TEMPO DE PARCELAS:
373
- {tempo_parcelas if tempo_parcelas else "Não foi possível calcular o tempo de parcelas"}
374
  """
375
  return resposta
376
 
377
- # Carrega a planilha uma vez quando o aplicativo inicia
378
  carregar_planilha()
379
 
380
- def ajustar_e_buscar(credito_desejado, entrada_desejada, parcela_desejada, administradoras):
381
- return buscar_combinacoes_otimizado(
382
- credito_desejado, entrada_desejada, parcela_desejada, administradoras, max_combinacoes=15
383
- )
384
-
385
- # Prepara a lista de administradoras para o Gradio
386
- def get_administradoras_options():
387
- if df is not None and planilha_carregada:
388
- adm_list = df[df['tipo_do_bem'].str.lower() == 'auto']['administradora'].unique()
389
- return sorted(adm_list)
390
- return []
391
-
392
- # Criando a interface Gradio
393
- with gr.Blocks(title="Sistema de Busca de Consórcio Auto") as demo:
394
- gr.HTML("""
395
- <link href="https://fonts.googleapis.com/css2?family=Poppins&display=swap" rel="stylesheet">
396
- <style>
397
- * {
398
- font-family: 'Poppins', sans-serif !important;
399
- }
400
-
401
- body {
402
- background-color: #ff8f00 !important;
403
- color: black !important;
404
- }
405
-
406
- /* Inputs Numéricos: Crédito, Entrada, Parcela */
407
- input[type="number"] {
408
- background-color: white !important;
409
- border: 2px solid #000 !important;
410
- border-radius: 12px !important;
411
- color: black !important;
412
- padding: 10px !important;
413
- font-weight: 500;
414
- }
415
-
416
- /* Inputs de texto e áreas de texto (como a saída e código) */
417
- input[type="text"], textarea {
418
- background-color: white !important;
419
- border: 2px solid black !important;
420
- border-radius: 12px !important;
421
- color: black !important;
422
- padding: 10px !important;
423
- }
424
-
425
- input::placeholder, textarea::placeholder {
426
- color: #333 !important;
427
- opacity: 0.7;
428
- }
429
-
430
- .gr-checkbox label {
431
- display: flex;
432
- align-items: center;
433
- gap: 8px;
434
- font-weight: 600;
435
- }
436
-
437
- input[type="checkbox"] {
438
- accent-color: black !important;
439
- width: 18px;
440
- height: 18px;
441
- }
442
-
443
- .gr-button {
444
- font-weight: bold;
445
- border-radius: 10px !important;
446
- background-color: white !important;
447
- color: black !important;
448
- border: 2px solid black !important;
449
- }
450
-
451
- .gr-button:hover {
452
- background-color: #fff3e0 !important;
453
- color: black !important;
454
- }
455
-
456
- label {
457
- font-weight: 600 !important;
458
- }
459
- </style>
460
- """)
461
-
462
-
463
- gr.Markdown("# 🚗 Busca de Combinações de Consórcio Auto")
464
- gr.Markdown("Insira os valores desejados para crédito, entrada e parcela, e filtre por administradora.")
465
-
466
- with gr.Row():
467
- credito_input = gr.Number(label="Crédito Desejado (R$)", value=30000, step=1000)
468
- entrada_input = gr.Number(label="Entrada Desejada (R$)", value=3000, step=100)
469
- parcela_input = gr.Number(label="Parcela Desejada (R$)", value=500, step=10)
470
-
471
- administradoras_multiselect = gr.CheckboxGroup(
472
- label="Filtrar por Administradoras",
473
- choices=get_administradoras_options(),
474
- value=get_administradoras_options() # Seleciona todas por padrão
475
- )
476
-
477
- submit_btn = gr.Button("🔍 Buscar Combinações", variant="primary")
478
-
479
- output_text = gr.Textbox(label="Resultados da Busca", lines=20, max_lines=40, interactive=False, placeholder="Os resultados aparecerão aqui...")
480
-
481
- submit_btn.click(
482
- fn=ajustar_e_buscar,
483
- inputs=[credito_input, entrada_input, parcela_input, administradoras_multiselect],
484
- outputs=output_text
485
- )
486
-
487
- gr.Markdown("---")
488
- gr.Markdown("Desenvolvido para auxiliar na busca por cotas de consórcio ideais.")
489
 
490
- # Inicia o aplicativo Gradio
491
  if __name__ == "__main__":
492
- demo.launch()
493
- #
 
1
+ import os
2
+ os.environ["GRADIO_ANALYTICS_ENABLED"] = "False"
3
+ os.environ["HF_HUB_DISABLE_TELEMETRY"] = "1"
4
  import pandas as pd
5
  import requests
6
  import io
7
  import itertools
8
  from collections import defaultdict
9
  import gradio as gr
 
 
10
 
 
11
  FILE_ID = "1EoH8V3Fyls0mNa95VcBN6ZL1B552ty3X"
12
  URL = f"https://drive.google.com/uc?export=download&id={FILE_ID}"
13
 
 
 
14
  df_auto = None
15
  grupos_auto = None
16
  planilha_carregada = False
17
+ ADMIN_OPCOES = []
18
 
19
  def carregar_planilha():
20
+ global df_auto, grupos_auto, planilha_carregada, ADMIN_OPCOES
 
 
 
21
  if planilha_carregada:
22
  return True
 
23
  try:
24
  print("⏳ Carregando planilha...")
25
  response = requests.get(URL)
26
  response.raise_for_status()
27
  data = io.BytesIO(response.content)
28
  df = pd.read_excel(data)
 
 
29
  df.columns = df.columns.str.strip().str.lower().str.replace(" ", "_")
 
 
 
 
 
 
30
  colunas_numericas = ['valor_credito', 'valor_entrada', 'valor_parcela', 'saldo_devedor', 'qtd_parcelas']
31
+ for col in colunas_numericas:
32
+ if col in df.columns:
33
+ df[col] = pd.to_numeric(df[col], errors='coerce').fillna(0)
 
 
34
  if 'tipo_do_bem' not in df.columns or 'administradora' not in df.columns:
35
  raise ValueError("Colunas essenciais não encontradas")
 
 
36
  df.drop_duplicates(inplace=True)
 
 
37
  df_auto = df[df['tipo_do_bem'].str.lower() == 'auto'].copy()
38
+ df_auto['administradora'] = df_auto['administradora'].astype(str).str.strip().str.lower()
39
+ df_auto = df_auto[df_auto['administradora'].notna() & (df_auto['administradora'] != '')]
40
+ grupos_auto = defaultdict(list)
41
+ for _, row in df_auto.iterrows():
42
+ grupos_auto[row['administradora']].append(row.to_dict())
43
+ ADMIN_OPCOES = sorted(df_auto['administradora'].unique())
44
  planilha_carregada = True
45
+ print(f"✅ Total de cotas: {len(df_auto)}")
46
+ print(f"✅ Administradoras: {ADMIN_OPCOES}")
 
47
  return True
 
48
  except Exception as e:
49
+ print(f"❌ Erro: {e}")
50
  return False
51
 
52
+ def formatar_moeda(valor):
53
+ return f"R$ {valor:,.2f}".replace(",", "v").replace(".", ",").replace("v", ".")
 
 
 
 
54
 
55
  def calcular_tempo_parcelas(combinacao):
 
56
  if not combinacao:
57
  return ""
 
58
  try:
 
59
  cotas_ordenadas = sorted(combinacao, key=lambda x: int(x.get('qtd_parcelas', 0)))
 
 
60
  prazos = sorted(set(int(c.get('qtd_parcelas', 0)) for c in combinacao))
 
61
  if not prazos or prazos[0] <= 0:
62
  return ""
 
63
  resultado = []
64
  prazo_anterior = 0
65
+ for prazo_atual in prazos:
 
 
66
  cotas_ativas = [c for c in cotas_ordenadas if int(c.get('qtd_parcelas', 0)) >= prazo_anterior + 1]
67
  valor_parcela_total = sum(c.get('valor_parcela', 0) for c in cotas_ativas)
 
68
  inicio = prazo_anterior + 1
69
  fim = prazo_atual
 
70
  if inicio <= fim and valor_parcela_total > 0:
71
  resultado.append(f"{inicio}x à {fim}x: {formatar_moeda(valor_parcela_total)}/mês")
 
72
  prazo_anterior = prazo_atual
 
 
73
  if prazos and prazo_anterior < max(prazos):
74
  cotas_ativas_finais = [c for c in cotas_ordenadas if int(c.get('qtd_parcelas', 0)) > prazo_anterior]
75
  if cotas_ativas_finais:
 
78
  fim_final = max(prazos)
79
  if valor_parcela_final > 0:
80
  resultado.append(f"{inicio_final}x à {fim_final}x: {formatar_moeda(valor_parcela_final)}/mês")
 
81
  return "\n".join(resultado) if resultado else ""
 
82
  except Exception as e:
83
+ print(f"Erro tempo: {e}")
84
  return ""
85
 
86
+ def buscar_combinacoes(credito, entrada, parcela, adm_selecionadas):
 
 
 
 
87
  if not planilha_carregada:
88
  if not carregar_planilha():
89
+ return "❌ Erro: planilha não carregada."
90
+ if adm_selecionadas:
91
+ grupos = {adm: cotas for adm, cotas in grupos_auto.items() if adm in adm_selecionadas}
 
 
 
 
92
  else:
93
+ grupos = grupos_auto
94
+ if not grupos:
95
+ return "🚫 Nenhuma cota encontrada com os filtros."
96
+
97
+ if credito > 5000000:
98
+ tol_cred, tol_ent, tol_par = 0.50, 0.50, 1.00
99
+ max_cotas = 25
100
+ elif credito > 100000:
101
+ tol_cred, tol_ent, tol_par = 0.25, 0.25, 0.50
102
+ max_cotas = 15
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
103
  else:
104
+ tol_cred, tol_ent, tol_par = 0.15, 0.15, 0.30
105
+ max_cotas = 15
 
 
106
 
107
+ cred_min = credito * (1 - tol_cred)
108
+ cred_max = credito * (1 + tol_cred)
109
+ ent_min = entrada * (1 - tol_ent)
110
+ ent_max = entrada * (1 + tol_ent)
111
+ parc_min = parcela * (1 - tol_par)
112
+ parc_max = parcela * (1 + tol_par)
 
 
 
113
 
114
  resultados = []
115
+ for adm, cotas in grupos.items():
116
+ for r in range(1, min(max_cotas + 1, len(cotas) + 1)):
117
+ for comb in itertools.combinations(cotas, r):
118
+ total_cred = sum(c['valor_credito'] for c in comb)
119
+ total_ent_base = sum(c['valor_entrada'] for c in comb)
120
+ total_ent = total_ent_base + total_cred * 0.085
121
+ total_parc = sum(c['valor_parcela'] for c in comb)
122
+ if (cred_min <= total_cred <= cred_max and
123
+ ent_min <= total_ent <= ent_max and
124
+ parc_min <= total_parc <= parc_max):
125
+ total_saldo = sum(c['saldo_devedor'] for c in comb)
126
+ valor_final = total_ent + total_saldo
127
+ diff = abs(valor_final - total_cred)
128
+ resultados.append((adm, comb, diff))
129
+ if len(resultados) >= 30:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
130
  break
131
+ if len(resultados) >= 30:
 
132
  break
133
+ if len(resultados) >= 30:
 
134
  break
135
 
 
 
 
136
  if not resultados:
137
+ return "🚫 Nenhuma combinação encontrada."
 
138
  resultados.sort(key=lambda x: x[2])
139
+ return formatar_resultados(resultados[:15])
 
 
 
140
 
141
  def formatar_resultados(resultados):
 
142
  if not resultados:
143
+ return "🚫 Nenhuma combinação adequada encontrada."
 
144
  resposta = ""
145
+ for idx, (adm, comb, _) in enumerate(resultados, 1):
146
+ codigos = [str(c.get('codigo', 'N/A')) for c in comb]
147
+ total_cred = sum(c['valor_credito'] for c in comb)
148
+ total_ent_base = sum(c['valor_entrada'] for c in comb)
149
+ total_ent = total_ent_base + total_cred * 0.085
150
+ total_saldo = sum(c['saldo_devedor'] for c in comb)
151
+ total_parc = sum(c['valor_parcela'] for c in comb)
152
+ maior_prazo = max(int(c.get('qtd_parcelas', 0)) for c in comb)
153
+ valor_final = total_ent + total_saldo
154
+ cred_real = total_cred - total_ent
155
+ taxa_total = total_saldo - cred_real
156
+ taxa_perc = (taxa_total / cred_real) * 100 if cred_real > 0 else 0
157
+ taxa_mensal = taxa_perc / maior_prazo if maior_prazo > 0 else 0
158
+ tempo = calcular_tempo_parcelas(comb)
 
 
 
 
159
  resposta += f"""
160
  --- COMBINAÇÃO {idx} ---
161
  🏢 Administradora: {adm}
162
+ 🔢 Códigos: {', '.join(codigos)} ({len(comb)} cotas)
163
+ 💰 Crédito Total: {formatar_moeda(total_cred)}
164
+ 💵 Entrada (+comissão): {formatar_moeda(total_ent)}
165
  📉 Saldo Devedor: {formatar_moeda(total_saldo)}
166
+ 💲 Parcela: {formatar_moeda(total_parc)}
167
  ⏳ Prazo: {maior_prazo} meses
168
  🔚 Valor Final: {formatar_moeda(valor_final)}
169
+ 💠 Crédito Líquido: {formatar_moeda(cred_real)}
170
+ 📊 Taxa Total: {taxa_perc:.2f}% ({formatar_moeda(taxa_total)})
171
  🗓️ Taxa Mensal: {taxa_mensal:.2f}%
172
  🗓️ Taxa Anual: {taxa_mensal * 12:.2f}%
173
  ⏰ TEMPO DE PARCELAS:
174
+ {tempo if tempo else "Não foi possível calcular"}
175
  """
176
  return resposta
177
 
178
+ # Carrega a planilha antes de iniciar
179
  carregar_planilha()
180
 
181
+ # Interface usando gr.Interface (API automática em /api/predict)
182
+ demo = gr.Interface(
183
+ fn=buscar_combinacoes,
184
+ inputs=[
185
+ gr.Number(label="Crédito Desejado (R$)", value=30000, step=1000),
186
+ gr.Number(label="Entrada Desejada (R$)", value=3000, step=100),
187
+ gr.Number(label="Parcela Desejada (R$)", value=500, step=10),
188
+ gr.Dropdown(
189
+ choices=ADMIN_OPCOES,
190
+ label="Filtrar por Administradoras",
191
+ multiselect=True,
192
+ value=ADMIN_OPCOES
193
+ )
194
+ ],
195
+ outputs=gr.Textbox(label="Resultados da Busca", lines=20, max_lines=40),
196
+ title="🚗 Busca de Combinações de Consórcio Auto",
197
+ description="Insira os valores desejados para crédito, entrada e parcela, e filtre por administradora."
198
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
199
 
 
200
  if __name__ == "__main__":
201
+ demo.launch()