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

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +396 -104
app.py CHANGED
@@ -1,75 +1,110 @@
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,124 +113,381 @@ def calcular_tempo_parcelas(combinacao):
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()
 
 
 
 
 
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
  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
+ #