Arthur-Attili commited on
Commit
b564964
·
verified ·
1 Parent(s): 5a5be0b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +148 -77
app.py CHANGED
@@ -8,6 +8,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
 
@@ -18,58 +19,101 @@ 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,111 +122,98 @@ 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
- """Versão otimizada: limita combinações e interrompe cedo."""
88
  if not planilha_carregada:
89
  if not carregar_planilha():
90
  return "❌ Erro: planilha não carregada."
91
-
92
- # Filtra administradoras selecionadas
93
  if adm_selecionadas:
94
  grupos = {adm: cotas for adm, cotas in grupos_auto.items() if adm in adm_selecionadas}
95
  else:
96
  grupos = grupos_auto
97
-
98
  if not grupos:
99
  return "🚫 Nenhuma cota encontrada com os filtros."
100
 
101
- # Configura tolerâncias e limites de acordo com o valor do crédito
102
- if credito > 5000000:
103
- tolerancia_credito = 0.50
104
- tolerancia_entrada = 0.50
105
- tolerancia_parcela = 1.00
106
- max_cotas_combinacao = 25
107
- max_combinacoes_por_adm = 7000
108
- elif credito > 100000:
109
- tolerancia_credito = 0.25
110
- tolerancia_entrada = 0.25
111
- tolerancia_parcela = 0.50
112
- max_cotas_combinacao = 15
113
- max_combinacoes_por_adm = 4000
114
- else:
115
- tolerancia_credito = 0.15
116
- tolerancia_entrada = 0.15
117
- tolerancia_parcela = 0.30
118
- max_cotas_combinacao = 15
119
- max_combinacoes_por_adm = 4000
120
-
121
- cred_min = credito * (1 - tolerancia_credito)
122
- cred_max = credito * (1 + tolerancia_credito)
123
- ent_min = entrada * (1 - tolerancia_entrada)
124
- ent_max = entrada * (1 + tolerancia_entrada)
125
- parc_min = parcela * (1 - tolerancia_parcela)
126
- parc_max = parcela * (1 + tolerancia_parcela)
127
 
128
  resultados = []
129
  total_combinacoes_testadas = 0
130
 
131
- # Para cada administradora
132
  for adm, cotas in grupos.items():
133
- print(f"🔍 Analisando administradora: {adm} ({len(cotas)} cotas)")
134
- cotas_para_combinar = cotas
135
-
136
- # Gera combinações de tamanho 1 até max_cotas_combinacao
137
- for r in range(1, min(max_cotas_combinacao + 1, len(cotas_para_combinar) + 1)):
138
  combinacoes_geradas = 0
139
- for combinacao in itertools.combinations(cotas_para_combinar, r):
140
  if combinacoes_geradas >= max_combinacoes_por_adm:
141
  break
142
-
143
  total_combinacoes_testadas += 1
144
  combinacoes_geradas += 1
145
-
146
  total_cred = sum(c['valor_credito'] for c in combinacao)
147
  total_ent_base = sum(c['valor_entrada'] for c in combinacao)
148
  total_ent = total_ent_base + total_cred * 0.085
149
  total_parc = sum(c['valor_parcela'] for c in combinacao)
150
-
151
  if (cred_min <= total_cred <= cred_max and
152
  ent_min <= total_ent <= ent_max and
153
  parc_min <= total_parc <= parc_max):
 
154
  total_saldo = sum(c['saldo_devedor'] for c in combinacao)
155
- valor_final = total_ent + total_saldo
156
  diff = abs(valor_final - total_cred)
 
157
  resultados.append((adm, combinacao, diff))
158
-
159
- # Se encontrou 3x o número desejado, interrompe busca
160
- if len(resultados) >= 45: # 15 * 3
161
  break
162
-
163
- # Sai do loop de r se já temos resultados suficientes
164
  if len(resultados) >= 45:
165
  break
166
-
167
- # Sai do loop de administradoras se já temos resultados suficientes
168
  if len(resultados) >= 45:
169
  break
170
 
171
  print(f"🔍 Total de combinações testadas: {total_combinacoes_testadas}")
172
- print(f"🔍 Combinações válidas encontradas: {len(resultados)}")
173
 
174
  if not resultados:
175
- # Tenta busca mais ampla (opcional, mas pode ser mantida)
176
- # Aqui poderia chamar uma função ampla, mas para simplicidade, retorna mensagem
177
  return "🚫 Nenhuma combinação encontrada com os parâmetros fornecidos."
178
 
179
- # Ordena pela diferença e pega as 15 melhores
180
- resultados.sort(key=lambda x: x[2])
181
  return formatar_resultados(resultados[:15])
182
 
 
183
  def formatar_resultados(resultados):
184
  if not resultados:
185
  return "🚫 Nenhuma combinação adequada encontrada."
 
186
  resposta = ""
187
  for idx, (adm, comb, _) in enumerate(resultados, 1):
188
  codigos = [str(c.get('codigo', 'N/A')) for c in comb]
@@ -192,12 +223,13 @@ def formatar_resultados(resultados):
192
  total_saldo = sum(c['saldo_devedor'] for c in comb)
193
  total_parc = sum(c['valor_parcela'] for c in comb)
194
  maior_prazo = max(int(c.get('qtd_parcelas', 0)) for c in comb)
195
- valor_final = total_ent + total_saldo
196
  cred_real = total_cred - total_ent
197
  taxa_total = total_saldo - cred_real
198
  taxa_perc = (taxa_total / cred_real) * 100 if cred_real > 0 else 0
199
  taxa_mensal = taxa_perc / maior_prazo if maior_prazo > 0 else 0
200
  tempo = calcular_tempo_parcelas(comb)
 
201
  resposta += f"""
202
  --- COMBINAÇÃO {idx} ---
203
  🏢 Administradora: {adm}
@@ -217,28 +249,67 @@ def formatar_resultados(resultados):
217
  """
218
  return resposta
219
 
220
- # Carrega a planilha uma vez no início
 
221
  carregar_planilha()
222
 
223
- # Interface Gradio (usando Blocks para maior controle, mas Interface também funciona)
224
- demo = gr.Interface(
225
- fn=buscar_combinacoes,
226
- inputs=[
227
- gr.Number(label="Crédito Desejado (R$)", value=30000, step=1000),
228
- gr.Number(label="Entrada Desejada (R$)", value=3000, step=100),
229
- gr.Number(label="Parcela Desejada (R$)", value=500, step=10),
230
- gr.Dropdown(
231
- choices=ADMIN_OPCOES,
232
- label="Filtrar por Administradoras",
233
- multiselect=True,
234
- value=ADMIN_OPCOES
235
- )
236
- ],
237
- outputs=gr.Textbox(label="Resultados da Busca", lines=20, max_lines=40),
238
- title="🚗 Busca de Combinações de Consórcio Auto (Otimizado)",
239
- description="Insira os valores desejados para crédito, entrada e parcela, e filtre por administradora."
240
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
241
 
 
242
  if __name__ == "__main__":
243
- demo.launch()
244
- #
 
8
  from collections import defaultdict
9
  import gradio as gr
10
 
11
+ # 🔧 CONFIGURAÇÕES
12
  FILE_ID = "1EoH8V3Fyls0mNa95VcBN6ZL1B552ty3X"
13
  URL = f"https://drive.google.com/uc?export=download&id={FILE_ID}"
14
 
 
19
 
20
  def carregar_planilha():
21
  global df_auto, grupos_auto, planilha_carregada, ADMIN_OPCOES
22
+
23
  if planilha_carregada:
24
  return True
25
+
26
  try:
27
  print("⏳ Carregando planilha...")
28
  response = requests.get(URL)
29
  response.raise_for_status()
30
  data = io.BytesIO(response.content)
31
  df = pd.read_excel(data)
32
+
33
  df.columns = df.columns.str.strip().str.lower().str.replace(" ", "_")
34
+
35
  colunas_numericas = ['valor_credito', 'valor_entrada', 'valor_parcela', 'saldo_devedor', 'qtd_parcelas']
36
  for col in colunas_numericas:
37
  if col in df.columns:
38
  df[col] = pd.to_numeric(df[col], errors='coerce').fillna(0)
39
+
40
  if 'tipo_do_bem' not in df.columns or 'administradora' not in df.columns:
41
  raise ValueError("Colunas essenciais não encontradas")
42
+
43
  df.drop_duplicates(inplace=True)
44
+
45
  df_auto = df[df['tipo_do_bem'].str.lower() == 'auto'].copy()
46
  df_auto['administradora'] = df_auto['administradora'].astype(str).str.strip().str.lower()
47
  df_auto = df_auto[df_auto['administradora'].notna() & (df_auto['administradora'] != '')]
48
+
49
  grupos_auto = defaultdict(list)
50
  for _, row in df_auto.iterrows():
51
  grupos_auto[row['administradora']].append(row.to_dict())
52
+
53
  ADMIN_OPCOES = sorted(df_auto['administradora'].unique())
54
  planilha_carregada = True
55
+
56
  print(f"✅ Total de cotas: {len(df_auto)}")
57
  print(f"✅ Administradoras: {ADMIN_OPCOES}")
58
  return True
59
+
60
  except Exception as e:
61
  print(f"❌ Erro: {e}")
62
  return False
63
 
64
+
65
  def formatar_moeda(valor):
66
  return f"R$ {valor:,.2f}".replace(",", "v").replace(".", ",").replace("v", ".")
67
 
68
+
69
+ def definir_margens_absolutas(credito, entrada, parcela):
70
+ """Margens fixas em R$ (ex.: 30.000 → 10.000 de margem total = 20k–40k)."""
71
+ margem_credito = 20_000
72
+ margem_entrada = 2_000
73
+ margem_parcela = 100
74
+
75
+ cred_min = max(0, credito - margem_credito)
76
+ cred_max = credito + margem_credito
77
+ ent_min = max(0, entrada - margem_entrada)
78
+ ent_max = entrada + margem_entrada
79
+ parc_min = max(0, parcela - margem_parcela)
80
+ parc_max = parcela + margem_parcela
81
+
82
+ return cred_min, cred_max, ent_min, ent_max, parc_min, parc_max
83
+
84
+
85
+ def filtrar_cotas_por_faixa(cotas, cred_min, cred_max):
86
+ """Remove cotas que não têm chance real de entrar na faixa de crédito."""
87
+ return [c for c in cotas if c['valor_credito'] < cred_max * 2]
88
+
89
+
90
  def calcular_tempo_parcelas(combinacao):
91
+ """Calcula o tempo de parcelas por faixa de prazo."""
92
  if not combinacao:
93
  return ""
94
+
95
  try:
96
  cotas_ordenadas = sorted(combinacao, key=lambda x: int(x.get('qtd_parcelas', 0)))
97
  prazos = sorted(set(int(c.get('qtd_parcelas', 0)) for c in combinacao))
98
+
99
  if not prazos or prazos[0] <= 0:
100
  return ""
101
+
102
  resultado = []
103
  prazo_anterior = 0
104
+
105
  for prazo_atual in prazos:
106
  cotas_ativas = [c for c in cotas_ordenadas if int(c.get('qtd_parcelas', 0)) >= prazo_anterior + 1]
107
  valor_parcela_total = sum(c.get('valor_parcela', 0) for c in cotas_ativas)
108
+
109
  inicio = prazo_anterior + 1
110
  fim = prazo_atual
111
+
112
  if inicio <= fim and valor_parcela_total > 0:
113
  resultado.append(f"{inicio}x à {fim}x: {formatar_moeda(valor_parcela_total)}/mês")
114
+
115
  prazo_anterior = prazo_atual
116
+
117
  if prazos and prazo_anterior < max(prazos):
118
  cotas_ativas_finais = [c for c in cotas_ordenadas if int(c.get('qtd_parcelas', 0)) > prazo_anterior]
119
  if cotas_ativas_finais:
 
122
  fim_final = max(prazos)
123
  if valor_parcela_final > 0:
124
  resultado.append(f"{inicio_final}x à {fim_final}x: {formatar_moeda(valor_parcela_final)}/mês")
125
+
126
+ return "\\n".join(resultado) if resultado else ""
127
+
128
  except Exception as e:
129
+ print(f"Erro calculando tempo de parcelas: {e}")
130
  return ""
131
 
132
+
133
  def buscar_combinacoes(credito, entrada, parcela, adm_selecionadas):
 
134
  if not planilha_carregada:
135
  if not carregar_planilha():
136
  return "❌ Erro: planilha não carregada."
137
+
138
+ # Filtra administradoras
139
  if adm_selecionadas:
140
  grupos = {adm: cotas for adm, cotas in grupos_auto.items() if adm in adm_selecionadas}
141
  else:
142
  grupos = grupos_auto
143
+
144
  if not grupos:
145
  return "🚫 Nenhuma cota encontrada com os filtros."
146
 
147
+ print(f"🔍 Total de administradoras: {len(grupos)}")
148
+
149
+ # Usa margens ABSOLUTAS em R$
150
+ cred_min, cred_max, ent_min, ent_max, parc_min, parc_max = definir_margens_absolutas(
151
+ float(credito or 0),
152
+ float(entrada or 0),
153
+ float(parcela or 0)
154
+ )
155
+
156
+ print(f"💰 Crédito desejado: R$ {credito:,.2f}")
157
+ print(f"🔍 Margem crédito: {cred_min:,.2f} – {cred_max:,.2f}")
158
+
159
+ # Limites de busca
160
+ max_cotas_combinacao = 15
161
+ max_combinacoes_por_adm = 4000
 
 
 
 
 
 
 
 
 
 
 
162
 
163
  resultados = []
164
  total_combinacoes_testadas = 0
165
 
 
166
  for adm, cotas in grupos.items():
167
+ print(f"🔍 Administradora: {adm} ({len(cotas)} cotas)")
168
+ cotas_filtradas = filtrar_cotas_por_faixa(cotas, cred_min, cred_max)
169
+
170
+ for r in range(1, min(max_cotas_combinacao + 1, len(cotas_filtradas) + 1)):
 
171
  combinacoes_geradas = 0
172
+ for combinacao in itertools.combinations(cotas_filtradas, r):
173
  if combinacoes_geradas >= max_combinacoes_por_adm:
174
  break
175
+
176
  total_combinacoes_testadas += 1
177
  combinacoes_geradas += 1
178
+
179
  total_cred = sum(c['valor_credito'] for c in combinacao)
180
  total_ent_base = sum(c['valor_entrada'] for c in combinacao)
181
  total_ent = total_ent_base + total_cred * 0.085
182
  total_parc = sum(c['valor_parcela'] for c in combinacao)
183
+
184
  if (cred_min <= total_cred <= cred_max and
185
  ent_min <= total_ent <= ent_max and
186
  parc_min <= total_parc <= parc_max):
187
+
188
  total_saldo = sum(c['saldo_devedor'] for c in combinacao)
189
+ valor_final = round(total_ent + total_saldo, 2)
190
  diff = abs(valor_final - total_cred)
191
+
192
  resultados.append((adm, combinacao, diff))
193
+
194
+ if len(resultados) >= 45:
 
195
  break
196
+
 
197
  if len(resultados) >= 45:
198
  break
199
+
 
200
  if len(resultados) >= 45:
201
  break
202
 
203
  print(f"🔍 Total de combinações testadas: {total_combinacoes_testadas}")
204
+ print(f"🔍 Resultados válidos: {len(resultados)}")
205
 
206
  if not resultados:
 
 
207
  return "🚫 Nenhuma combinação encontrada com os parâmetros fornecidos."
208
 
209
+ resultados.sort(key=lambda x: x[2]) # menor diferença
 
210
  return formatar_resultados(resultados[:15])
211
 
212
+
213
  def formatar_resultados(resultados):
214
  if not resultados:
215
  return "🚫 Nenhuma combinação adequada encontrada."
216
+
217
  resposta = ""
218
  for idx, (adm, comb, _) in enumerate(resultados, 1):
219
  codigos = [str(c.get('codigo', 'N/A')) for c in comb]
 
223
  total_saldo = sum(c['saldo_devedor'] for c in comb)
224
  total_parc = sum(c['valor_parcela'] for c in comb)
225
  maior_prazo = max(int(c.get('qtd_parcelas', 0)) for c in comb)
226
+ valor_final = round(total_ent + total_saldo, 2)
227
  cred_real = total_cred - total_ent
228
  taxa_total = total_saldo - cred_real
229
  taxa_perc = (taxa_total / cred_real) * 100 if cred_real > 0 else 0
230
  taxa_mensal = taxa_perc / maior_prazo if maior_prazo > 0 else 0
231
  tempo = calcular_tempo_parcelas(comb)
232
+
233
  resposta += f"""
234
  --- COMBINAÇÃO {idx} ---
235
  🏢 Administradora: {adm}
 
249
  """
250
  return resposta
251
 
252
+
253
+ # Carrega uma vez
254
  carregar_planilha()
255
 
256
+ # Interface Gradio (com Blocks, igual ao imóvel)
257
+ with gr.Blocks(title="🚗 Busca de Consórcio Auto (Otimizado)") as demo:
258
+ gr.HTML("""
259
+ <link href="https://fonts.googleapis.com/css2?family=Poppins&display=swap" rel="stylesheet">
260
+ <style>
261
+ * { font-family: 'Poppins', sans-serif !important; }
262
+ body { background-color: #ff8f00 !important; color: black !important; }
263
+ input[type="number"], textarea {
264
+ background-color: white !important;
265
+ border: 2px solid black !important;
266
+ border-radius: 12px !important;
267
+ color: black !important;
268
+ padding: 10px !important;
269
+ }
270
+ .gr-button {
271
+ font-weight: bold;
272
+ border-radius: 10px !important;
273
+ background-color: white !important;
274
+ color: black !important;
275
+ border: 2px solid black !important;
276
+ }
277
+ </style>
278
+ """)
279
+
280
+ gr.Markdown("# 🚗 Busca de Combinações de Consórcio Auto")
281
+ gr.Markdown("Insira os valores desejados para crédito, entrada e parcela, e filtre por administradora.")
282
+
283
+ with gr.Row():
284
+ credito_input = gr.Number(label="Crédito Desejado (R$)", value=30000, step=1000)
285
+ entrada_input = gr.Number(label="Entrada Desejada (R$)", value=3000, step=100)
286
+ parcela_input = gr.Number(label="Parcela Desejada (R$)", value=500, step=10)
287
+
288
+ adm_input = gr.Dropdown(
289
+ choices=ADMIN_OPCOES,
290
+ label="Filtrar por Administradoras",
291
+ multiselect=True,
292
+ value=ADMIN_OPCOES
293
+ )
294
+
295
+ submit_btn = gr.Button("🔍 Buscar Combinações", variant="primary")
296
+ output_text = gr.Textbox(
297
+ label="Resultados da Busca",
298
+ lines=20,
299
+ max_lines=40,
300
+ interactive=False,
301
+ placeholder="Os resultados aparecerão aqui..."
302
+ )
303
+
304
+ submit_btn.click(
305
+ fn=buscar_combinacoes,
306
+ inputs=[credito_input, entrada_input, parcela_input, adm_input],
307
+ outputs=output_text
308
+ )
309
+
310
+ gr.Markdown("---")
311
+ gr.Markdown("Desenvolvido para auxiliar na busca por cotas de consórcio automotivo.")
312
 
313
+ # Inicia o app
314
  if __name__ == "__main__":
315
+ demo.launch()