Arthur-Attili commited on
Commit
758c4b7
·
verified ·
1 Parent(s): 4d5fbc0

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +442 -86
app.py CHANGED
@@ -4,133 +4,489 @@ import io
4
  import itertools
5
  from collections import defaultdict
6
  import gradio as gr
 
 
7
 
8
- # 🔧 CONFIGURAÇÕES GLOBAIS - Planilha de AUTO
9
  FILE_ID = "1EoH8V3Fyls0mNa95VcBN6ZL1B552ty3X"
10
  URL = f"https://drive.google.com/uc?export=download&id={FILE_ID}"
11
 
12
- # Variáveis globais
 
13
  df_auto = None
14
  grupos_auto = None
15
  planilha_carregada = False
16
 
17
- def formatar_moeda(valor):
18
- return f"R$ {valor:,.2f}".replace(",", "v").replace(".", ",").replace("v", ".")
19
-
20
  def carregar_planilha():
21
- """Carrega e prepara a planilha de Automóveis."""
22
- global df_auto, grupos_auto, planilha_carregada
 
 
23
  if planilha_carregada:
24
  return True
 
25
  try:
 
26
  response = requests.get(URL)
27
  response.raise_for_status()
28
- df = pd.read_excel(io.BytesIO(response.content))
29
-
30
- # Padronização de colunas
 
31
  df.columns = df.columns.str.strip().str.lower().str.replace(" ", "_")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
 
33
- colunas_num = ['valor_credito', 'valor_entrada', 'valor_parcela', 'saldo_devedor', 'qtd_parcelas']
34
- for col in colunas_num:
35
- if col in df.columns:
36
- df[col] = pd.to_numeric(df[col], errors='coerce').fillna(0)
37
-
38
- # Filtra apenas o que for AUTO (conforme sua planilha)
39
- df_auto = df[df['tipo_do_bem'].str.lower().str.contains('auto', na=False)].copy()
40
-
41
- # Agrupamento por Administradora
42
- grupos_auto = defaultdict(list)
43
- for _, row in df_auto.iterrows():
44
- grupos_auto[row['administradora']].append(row.to_dict())
45
-
46
  planilha_carregada = True
 
 
 
47
  return True
 
48
  except Exception as e:
49
- print(f"❌ Erro ao carregar: {e}")
50
  return False
51
 
52
- def buscar_auto(credito_desejado, entrada_desejada, parcela_desejada, adms_selecionadas):
53
- """Lógica de busca otimizada para Automóveis."""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
  if not planilha_carregada:
55
- carregar_planilha()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
 
57
- # Filtro de administradoras
58
- grupos = {k: v for k, v in grupos_auto.items() if k in adms_selecionadas} if adms_selecionadas else grupos_auto
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
 
60
- if not grupos:
61
- return "🚫 Nenhuma administradora selecionada ou encontrada."
 
 
 
 
62
 
63
  resultados = []
64
- # Tolerância de 25% para facilitar o encontro de combinações
65
- tol = 0.25
66
-
67
- for adm, cotas in grupos.items():
68
- # Limita a combinação de até 3 cotas para não travar a memória do servidor
69
- for r in range(1, min(4, len(cotas) + 1)):
70
- for combo in itertools.combinations(cotas, r):
71
- t_cred = sum(c['valor_credito'] for c in combo)
72
- # Cálculo da entrada: Entrada da cota + 8.5% do crédito (comissão)
73
- t_ent = sum(c['valor_entrada'] for c in combo) + (t_cred * 0.085)
74
- t_parc = sum(c['valor_parcela'] for c in combo)
75
-
76
- if (credito_desejado*(1-tol) <= t_cred <= credito_desejado*(1+tol) and
77
- t_ent <= entrada_desejada * 1.5 and
78
- t_parc <= parcela_desejada * 1.5):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
79
 
80
- dif = abs(t_cred - credito_desejado)
81
- resultados.append((adm, combo, dif))
 
82
 
83
- if len(resultados) > 30: break
 
 
 
 
 
 
 
84
 
85
  if not resultados:
86
- return "🚫 Nenhuma combinação encontrada com esses valores. Tente aumentar a entrada ou a parcela."
 
 
87
 
88
- # Ordena pela proximidade do crédito desejado
89
  resultados.sort(key=lambda x: x[2])
 
 
 
 
 
90
 
91
- # Formatação da String de Saída (Evita o erro 'unhashable type: dict')
92
- saida = ""
93
- for idx, (adm, combo, _) in enumerate(resultados[:15], 1):
94
- codigos = [str(c.get('codigo', 'N/A')) for c in combo]
95
- t_cred = sum(c['valor_credito'] for c in combo)
96
- t_ent = sum(c['valor_entrada'] for c in combo) + (t_cred * 0.085)
97
- t_parc = sum(c['valor_parcela'] for c in combo)
98
- prazo = max(int(c['qtd_parcelas']) for c in combo)
99
-
100
- saida += f"--- COMBINAÇÃO {idx} ---\n"
101
- saida += f"🏢 Administradora: {adm}\n"
102
- saida += f"🔢 Códigos: {', '.join(codigos)}\n"
103
- saida += f"💰 Crédito Total: {formatar_moeda(t_cred)}\n"
104
- saida += f"💵 Entrada (+comissão): {formatar_moeda(t_ent)}\n"
105
- saida += f"💲 Parcela Total: {formatar_moeda(t_parc)}\n"
106
- saida += f"⏳ Prazo: {prazo} meses\n\n"
107
-
108
- return saida
109
-
110
- # --- INTERFACE GRADIO ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
111
  carregar_planilha()
112
- opcoes_adm = sorted(list(grupos_auto.keys())) if grupos_auto else []
113
 
114
- with gr.Blocks(title="Buscador Consórcio Auto") as demo:
115
- gr.Markdown("# 🚗 Busca de Cotas de Automóvel")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
116
 
117
- with gr.Row():
118
- cred_in = gr.Number(label="Crédito Desejado (R$)", value=80000)
119
- ent_in = gr.Number(label="Entrada Disponível (R$)", value=15000)
120
- parc_in = gr.Number(label="Parcela Máxima (R$)", value=1200)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
121
 
122
- adm_in = gr.CheckboxGroup(choices=opcoes_adm, label="Selecione as Administradoras", value=opcoes_adm)
 
 
 
 
 
123
 
124
- btn = gr.Button("🔍 BUSCAR MELHORES OPÇÕES", variant="primary")
 
 
 
 
125
 
126
- # Textbox para exibir o texto formatado (resolvendo o erro de dict)
127
- out_text = gr.Textbox(label="Resultados Encontrados", lines=25, interactive=False)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
128
 
129
- btn.click(
130
- fn=buscar_auto,
131
- inputs=[cred_in, ent_in, parc_in, adm_in],
132
- outputs=out_text
133
  )
134
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
135
  if __name__ == "__main__":
136
  demo.launch()
 
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:
111
+ valor_parcela_final = sum(c.get('valor_parcela', 0) for c in cotas_ativas_finais)
112
+ inicio_final = prazo_anterior + 1
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 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()