Arthur-Attili commited on
Commit
04666e9
·
verified ·
1 Parent(s): 3410ffa

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +122 -54
app.py CHANGED
@@ -23,7 +23,7 @@ URL = f"https://drive.google.com/uc?export=download&id={FILE_ID}"
23
  df = None
24
 
25
  def carregar_planilha():
26
- """Carrega e prepara a planilha para análise."""
27
  global df
28
  try:
29
  print("⏳ Carregando planilha...")
@@ -35,24 +35,29 @@ def carregar_planilha():
35
  # Padroniza nomes das colunas
36
  df.columns = df.columns.str.strip().str.lower().str.replace(" ", "_")
37
 
 
 
 
 
 
38
  # Converte colunas numéricas
39
- colunas_numericas = ['valor_credito', 'valor_entrada', 'valor_parcela', 'saldo_devedor', 'qtd_parcelas']
40
- for coluna in colunas_numericas:
41
- if coluna in df.columns:
42
- df[coluna] = pd.to_numeric(df[coluna], errors='coerce').fillna(0)
43
 
44
  # Verifica colunas essenciais
45
  if 'tipo_do_bem' not in df.columns or 'administradora' not in df.columns:
46
- raise ValueError("Colunas essenciais não encontradas")
47
 
48
  # Remove duplicatas
49
  df.drop_duplicates(inplace=True)
50
-
51
- print("✅ Planilha carregada com sucesso")
52
  return True
53
 
54
  except Exception as e:
55
  print(f"❌ Erro ao carregar planilha: {str(e)}")
 
56
  return False
57
 
58
  def agrupar_por_administradora(dataframe):
@@ -63,34 +68,36 @@ def agrupar_por_administradora(dataframe):
63
  return grupos
64
 
65
  def buscar_combinacoes(credito_desejado, entrada_desejada, parcela_desejada, administradoras=None):
66
- """Busca combinações de cotas dentro dos parâmetros especificados."""
67
- # Garante que o DataFrame está carregado
 
 
68
  if df is None:
69
- # Tenta carregar se ainda não foi carregado
70
  if not carregar_planilha():
71
- return "❌ Erro: Planilha não carregada. Por favor, tente novamente."
72
 
73
- # Filtra por tipo de bem e administradoras
74
- df_filtrado = df[df['tipo_do_bem'].str.lower() == 'imovel']
75
  if administradoras:
76
- # Certifica-se de que administradoras é uma lista (Gradio retorna tupla)
77
- if isinstance(administradoras, tuple):
78
  administradoras = list(administradoras)
 
79
  df_filtrado = df_filtrado[df_filtrado['administradora'].isin(administradoras)]
80
 
 
81
  if df_filtrado.empty:
82
- return "🚫 Nenhuma cota encontrada com os filtros aplicados. Tente ajustar os parâmetros."
83
 
84
  # Define faixas de tolerância
85
  credito_min, credito_max = credito_desejado * 0.93, credito_desejado * 1.07
86
  entrada_min, entrada_max = entrada_desejada * 0.93, entrada_desejada * 1.07
87
- parcela_min, parcela_max = parcela_desejada * 0.75, parcela_desejada * 1.25
88
 
89
  grupos = agrupar_por_administradora(df_filtrado)
90
  resultados = []
91
 
92
  for adm, cotas in grupos.items():
93
- for r in range(1, min(3, len(cotas)) + 1): # Máximo 3 cotas por combinação
94
  for combinacao in itertools.combinations(cotas, r):
95
  total_credito = sum(c['valor_credito'] for c in combinacao)
96
  total_entrada_base = sum(c['valor_entrada'] for c in combinacao)
@@ -103,15 +110,14 @@ def buscar_combinacoes(credito_desejado, entrada_desejada, parcela_desejada, adm
103
 
104
  total_saldo = sum(c['saldo_devedor'] for c in combinacao)
105
  valor_final = round(total_entrada + total_saldo, 2)
106
- diferenca = abs(valor_final - total_credito) # Diferença do valor final pelo crédito (para ordenação)
107
 
108
  resultados.append((adm, combinacao, diferenca))
109
 
110
  if not resultados:
111
- return "🚫 Nenhuma combinação adequada encontrada com os parâmetros fornecidos."
112
-
113
- resultados.sort(key=lambda x: x[2]) # Ordena pela diferença
114
 
 
115
  return formatar_resultados(resultados)
116
 
117
  def formatar_moeda(valor):
@@ -120,8 +126,62 @@ def formatar_moeda(valor):
120
  def formatar_resultados(resultados):
121
  """Formata os resultados para exibição."""
122
  resposta = ""
123
- # Limita a 15 resultados para evitar saídas muito longas
124
- for idx, (adm, combinacao, _) in enumerate(resultados[:15], 1):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
125
  codigos = [str(c.get('codigo', 'N/A')) for c in combinacao]
126
  total_credito = sum(c['valor_credito'] for c in combinacao)
127
  total_entrada_base = sum(c['valor_entrada'] for c in combinacao)
@@ -136,12 +196,14 @@ def formatar_resultados(resultados):
136
  taxa_percentual = (taxa_total / credito_real) * 100 if credito_real > 0 else 0
137
  taxa_mensal = taxa_percentual / maior_prazo if maior_prazo > 0 else 0
138
 
 
 
139
  resposta += f"""
140
  --- COMBINAÇÃO {idx} ---
141
- 🏢 Administradora: {adm}
142
  🔢 Códigos: {', '.join(codigos)}
143
  💰 Crédito Total: {formatar_moeda(total_credito)}
144
- 💵 Entrada (+comissão): {formatar_moeda(total_entrada)}
145
  📉 Saldo Devedor: {formatar_moeda(total_saldo)}
146
  💲 Parcela: {formatar_moeda(total_parcelas)}
147
  ⏳ Prazo: {maior_prazo} meses
@@ -153,34 +215,38 @@ def formatar_resultados(resultados):
153
  """
154
  return resposta
155
 
156
- # Carrega a planilha uma vez quando o aplicativo inicia
157
- carregar_planilha()
 
 
 
158
 
 
159
  def ajustar_e_buscar(credito_desejado, entrada_desejada, parcela_desejada, administradoras):
160
  entrada_calculada = credito_desejado * 0.085 + entrada_desejada
161
  return buscar_combinacoes(credito_desejado, entrada_calculada, parcela_desejada, administradoras)
162
 
163
- # Prepara a lista de administradoras para o Gradio
164
  def get_administradoras_options():
165
  if df is not None:
166
- adm_list = df[df['tipo_do_bem'].str.lower() == 'imovel']['administradora'].unique()
167
- return sorted(adm_list)
168
  return []
169
 
170
- # Criando a interface Gradio
171
- with gr.Blocks(title="Sistema de Busca de Consórcio Imóvel") as demo:
172
  gr.HTML("""
173
  <link href="https://fonts.googleapis.com/css2?family=Poppins&display=swap" rel="stylesheet">
174
  <style>
175
  * {
176
  font-family: 'Poppins', sans-serif !important;
177
  }
178
-
179
  body {
180
  background-color: #ff8f00 !important;
181
  color: black !important;
182
  }
183
-
184
  /* Inputs Numéricos: Crédito, Entrada, Parcela */
185
  input[type="number"] {
186
  background-color: white !important;
@@ -190,7 +256,7 @@ with gr.Blocks(title="Sistema de Busca de Consórcio Imóvel") as demo:
190
  padding: 10px !important;
191
  font-weight: 500;
192
  }
193
-
194
  /* Inputs de texto e áreas de texto (como a saída e código) */
195
  input[type="text"], textarea {
196
  background-color: white !important;
@@ -199,25 +265,25 @@ with gr.Blocks(title="Sistema de Busca de Consórcio Imóvel") as demo:
199
  color: black !important;
200
  padding: 10px !important;
201
  }
202
-
203
  input::placeholder, textarea::placeholder {
204
  color: #333 !important;
205
  opacity: 0.7;
206
  }
207
-
208
  .gr-checkbox label {
209
  display: flex;
210
  align-items: center;
211
  gap: 8px;
212
  font-weight: 600;
213
  }
214
-
215
  input[type="checkbox"] {
216
  accent-color: black !important;
217
  width: 18px;
218
  height: 18px;
219
  }
220
-
221
  .gr-button {
222
  font-weight: bold;
223
  border-radius: 10px !important;
@@ -225,46 +291,48 @@ with gr.Blocks(title="Sistema de Busca de Consórcio Imóvel") as demo:
225
  color: black !important;
226
  border: 2px solid black !important;
227
  }
228
-
229
  .gr-button:hover {
230
  background-color: #fff3e0 !important;
231
  color: black !important;
232
  }
233
-
234
  label {
235
  font-weight: 600 !important;
236
  }
237
  </style>
238
  """)
239
-
240
-
241
- gr.Markdown("# 🏠 Busca de Combinações de Consórcio Imóvel")
242
- gr.Markdown("Insira os valores desejados para crédito, entrada e parcela, e filtre por administradora.")
243
 
244
  with gr.Row():
245
- credito_input = gr.Number(label="Crédito Desejado (R$)", value=300000, step=1000)
246
- entrada_input = gr.Number(label="Entrada Desejada (R$)", value=30000, step=1000)
247
- parcela_input = gr.Number(label="Parcela Desejada (R$)", value=1500, step=100)
248
 
 
249
  administradoras_multiselect = gr.CheckboxGroup(
250
  label="Filtrar por Administradoras",
251
- choices=get_administradoras_options(),
252
- value=get_administradoras_options() # Seleciona todas por padrão
 
253
  )
254
 
255
  submit_btn = gr.Button("🔍 Buscar Combinações", variant="primary")
256
 
257
  output_text = gr.Textbox(label="Resultados da Busca", lines=20, max_lines=40, interactive=False, placeholder="Os resultados aparecerão aqui...")
258
 
 
259
  submit_btn.click(
260
  fn=ajustar_e_buscar,
261
  inputs=[credito_input, entrada_input, parcela_input, administradoras_multiselect],
262
  outputs=output_text
263
  )
264
 
 
265
  gr.Markdown("---")
266
- gr.Markdown("Desenvolvido para auxiliar na busca por cotas de consórcio ideais.")
267
 
268
- # Inicia o aplicativo Gradio
269
  if __name__ == "__main__":
270
  demo.launch() # Você não precisa disso no Colab, mas sim no arquivo que você fará o deploy
 
23
  df = None
24
 
25
  def carregar_planilha():
26
+ """Carrega e prepara a planilha para análise de consórcios de automóveis."""
27
  global df
28
  try:
29
  print("⏳ Carregando planilha...")
 
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
+
43
  # Converte colunas numéricas
44
+ numeric_cols = ['valor_credito', 'valor_entrada', 'valor_parcela', 'saldo_devedor', 'qtd_parcelas']
45
+ for col in numeric_cols:
46
+ if col in df.columns:
47
+ df[col] = pd.to_numeric(df[col], errors='coerce').fillna(0)
48
 
49
  # Verifica colunas essenciais
50
  if 'tipo_do_bem' not in df.columns or 'administradora' not in df.columns:
51
+ raise ValueError("Colunas essenciais não encontradas na planilha")
52
 
53
  # Remove duplicatas
54
  df.drop_duplicates(inplace=True)
55
+ print("✅ Planilha carregada com sucesso!")
 
56
  return True
57
 
58
  except Exception as e:
59
  print(f"❌ Erro ao carregar planilha: {str(e)}")
60
+ # Retorne uma mensagem de erro mais útil para o usuário
61
  return False
62
 
63
  def agrupar_por_administradora(dataframe):
 
68
  return grupos
69
 
70
  def buscar_combinacoes(credito_desejado, entrada_desejada, parcela_desejada, administradoras=None):
71
+ """Busca combinações de cotas para automóveis dentro dos parâmetros especificados."""
72
+ # Garante que o DataFrame está carregado antes de cada busca
73
+ # Isso é útil caso a sessão expire ou o DF seja limpo por algum motivo no servidor do HF.
74
+ global df
75
  if df is None:
 
76
  if not carregar_planilha():
77
+ return "❌ Erro: Planilha não carregada. Verifique se o ID do arquivo está correto e público."
78
 
79
+ # Filtra por tipo de bem (Auto) e administradoras selecionadas
80
+ df_filtrado = df[df['tipo_do_bem'].str.lower() == 'auto']
81
  if administradoras:
82
+ if isinstance(administradoras, tuple): # Gradio SelectMultiple retorna uma tupla
 
83
  administradoras = list(administradoras)
84
+ administradoras = [a.strip().lower() for a in administradoras] # Normaliza os nomes
85
  df_filtrado = df_filtrado[df_filtrado['administradora'].isin(administradoras)]
86
 
87
+
88
  if df_filtrado.empty:
89
+ return "🚫 Nenhuma cota de automóvel encontrada com os filtros aplicados."
90
 
91
  # Define faixas de tolerância
92
  credito_min, credito_max = credito_desejado * 0.93, credito_desejado * 1.07
93
  entrada_min, entrada_max = entrada_desejada * 0.93, entrada_desejada * 1.07
94
+ parcela_min, parcela_max = parcela_desejada * 0.5, parcela_desejada * 1.5
95
 
96
  grupos = agrupar_por_administradora(df_filtrado)
97
  resultados = []
98
 
99
  for adm, cotas in grupos.items():
100
+ for r in range(1, min(3, len(cotas)) + 1): # Máximo 3 cotas por combinação
101
  for combinacao in itertools.combinations(cotas, r):
102
  total_credito = sum(c['valor_credito'] for c in combinacao)
103
  total_entrada_base = sum(c['valor_entrada'] for c in combinacao)
 
110
 
111
  total_saldo = sum(c['saldo_devedor'] for c in combinacao)
112
  valor_final = round(total_entrada + total_saldo, 2)
113
+ diferenca = abs(valor_final - total_credito)
114
 
115
  resultados.append((adm, combinacao, diferenca))
116
 
117
  if not resultados:
118
+ return "Nenhuma combinação adequada encontrada para automóveis com os parâmetros fornecidos."
 
 
119
 
120
+ resultados.sort(key=lambda x: x[2])
121
  return formatar_resultados(resultados)
122
 
123
  def formatar_moeda(valor):
 
126
  def formatar_resultados(resultados):
127
  """Formata os resultados para exibição."""
128
  resposta = ""
129
+
130
+ # 1. Agrupar os resultados por administradora, mantendo a diferença para ordenação interna
131
+ # grouped_by_adm = {'Administradora X': [(combinacao_A, 100), (combinacao_C, 180), ...],
132
+ # 'Administradora Y': [(combinacao_B, 150), ...]}
133
+ grouped_by_adm = defaultdict(list)
134
+ for adm, combinacao, diferenca in resultados:
135
+ grouped_by_adm[adm].append((combinacao, diferenca))
136
+
137
+ # 2. Ordenar as combinações dentro de cada administradora pela diferença (do menor para o maior)
138
+ for adm in grouped_by_adm:
139
+ grouped_by_adm[adm].sort(key=lambda x: x[1]) # x[1] é a diferença
140
+
141
+ final_resultados_diversificados = []
142
+
143
+ # Dicionário para acompanhar o índice da próxima combinação a ser pega de cada administradora
144
+ # Ex: {'Itau': 0, 'Bradesco': 0, 'Caixa': 0}
145
+ adm_indices = {adm: 0 for adm in grouped_by_adm.keys()}
146
+
147
+ # Lista de administradoras ativas (que ainda têm combinações disponíveis)
148
+ active_adms = list(grouped_by_adm.keys())
149
+
150
+ current_adm_idx = 0 # Índice para ciclar entre as administradoras ativas
151
+
152
+ # Loop para selecionar até 15 combinações, garantindo diversidade
153
+ while len(final_resultados_diversificados) < 15 and active_adms:
154
+ # Pega a administradora da vez para selecionar uma combinação
155
+ adm_da_vez = active_adms[current_adm_idx]
156
+
157
+ # Pega o índice da próxima combinação para essa administradora
158
+ idx_na_adm = adm_indices[adm_da_vez]
159
+
160
+ # Verifica se essa administradora ainda tem combinações disponíveis
161
+ if idx_na_adm < len(grouped_by_adm[adm_da_vez]):
162
+ # Pega a combinação (ignorando a diferença, que já foi usada para ordenar internamente)
163
+ combinacao_data, _ = grouped_by_adm[adm_da_vez][idx_na_adm]
164
+
165
+ # Adiciona à lista de resultados final (com a administradora e a combinação)
166
+ final_resultados_diversificados.append((adm_da_vez, combinacao_data))
167
+
168
+ # Incrementa o índice para a próxima vez que pegarmos dessa administradora
169
+ adm_indices[adm_da_vez] += 1
170
+ else:
171
+ # Se essa administradora não tem mais combinações, remove ela da lista de ativas
172
+ active_adms.pop(current_adm_idx)
173
+ # Ajusta o current_adm_idx se a lista diminuiu e estamos no limite
174
+ if current_adm_idx >= len(active_adms) and active_adms:
175
+ current_adm_idx = 0 # Volta para o início da lista se removemos o último
176
+
177
+ # Move para a próxima administradora no ciclo (garantindo que não estouramos o índice da lista active_adms)
178
+ if active_adms: # Só avança se ainda houver administradoras ativas
179
+ current_adm_idx = (current_adm_idx + 1) % len(active_adms)
180
+ else:
181
+ break # Não há mais administradoras ativas, sai do loop
182
+
183
+ # Agora, itera sobre os resultados diversificados para formatar a resposta
184
+ for idx, (adm, combinacao) in enumerate(final_resultados_diversificados, 1):
185
  codigos = [str(c.get('codigo', 'N/A')) for c in combinacao]
186
  total_credito = sum(c['valor_credito'] for c in combinacao)
187
  total_entrada_base = sum(c['valor_entrada'] for c in combinacao)
 
196
  taxa_percentual = (taxa_total / credito_real) * 100 if credito_real > 0 else 0
197
  taxa_mensal = taxa_percentual / maior_prazo if maior_prazo > 0 else 0
198
 
199
+ #comissao = total_entrada * (8.5 / 108.5)
200
+
201
  resposta += f"""
202
  --- COMBINAÇÃO {idx} ---
203
+ �� Administradora: {adm}
204
  🔢 Códigos: {', '.join(codigos)}
205
  💰 Crédito Total: {formatar_moeda(total_credito)}
206
+ �� Entrada (+comissão): {formatar_moeda(total_entrada)}
207
  📉 Saldo Devedor: {formatar_moeda(total_saldo)}
208
  💲 Parcela: {formatar_moeda(total_parcelas)}
209
  ⏳ Prazo: {maior_prazo} meses
 
215
  """
216
  return resposta
217
 
218
+ # --- INICIALIZAÇÃO PARA GRADIO ---
219
+ # Carrega a planilha UMA VEZ no início do aplicativo
220
+ # Se der erro, a função de busca tentará carregar novamente.
221
+ if not carregar_planilha():
222
+ print("⚠️ Aviso: Planilha não carregada na inicialização. Tentará carregar na primeira busca.")
223
 
224
+ # Calcula a entrada adicionando 8,5% ao valor final da entrada.
225
  def ajustar_e_buscar(credito_desejado, entrada_desejada, parcela_desejada, administradoras):
226
  entrada_calculada = credito_desejado * 0.085 + entrada_desejada
227
  return buscar_combinacoes(credito_desejado, entrada_calculada, parcela_desejada, administradoras)
228
 
229
+ # Prepara as opções para o seletor de administradoras do Gradio
230
  def get_administradoras_options():
231
  if df is not None:
232
+ adm_list = df[df['tipo_do_bem'].str.lower() == 'auto']['administradora'].unique()
233
+ return sorted(adm_list.tolist()) # Converter para lista, pois .unique() pode retornar um numpy array
234
  return []
235
 
236
+ # --- Interface Gradio ---
237
+ with gr.Blocks(title="Sistema de Busca de Consórcio de Automóveis") as demo:
238
  gr.HTML("""
239
  <link href="https://fonts.googleapis.com/css2?family=Poppins&display=swap" rel="stylesheet">
240
  <style>
241
  * {
242
  font-family: 'Poppins', sans-serif !important;
243
  }
244
+
245
  body {
246
  background-color: #ff8f00 !important;
247
  color: black !important;
248
  }
249
+
250
  /* Inputs Numéricos: Crédito, Entrada, Parcela */
251
  input[type="number"] {
252
  background-color: white !important;
 
256
  padding: 10px !important;
257
  font-weight: 500;
258
  }
259
+
260
  /* Inputs de texto e áreas de texto (como a saída e código) */
261
  input[type="text"], textarea {
262
  background-color: white !important;
 
265
  color: black !important;
266
  padding: 10px !important;
267
  }
268
+
269
  input::placeholder, textarea::placeholder {
270
  color: #333 !important;
271
  opacity: 0.7;
272
  }
273
+
274
  .gr-checkbox label {
275
  display: flex;
276
  align-items: center;
277
  gap: 8px;
278
  font-weight: 600;
279
  }
280
+
281
  input[type="checkbox"] {
282
  accent-color: black !important;
283
  width: 18px;
284
  height: 18px;
285
  }
286
+
287
  .gr-button {
288
  font-weight: bold;
289
  border-radius: 10px !important;
 
291
  color: black !important;
292
  border: 2px solid black !important;
293
  }
294
+
295
  .gr-button:hover {
296
  background-color: #fff3e0 !important;
297
  color: black !important;
298
  }
299
+
300
  label {
301
  font-weight: 600 !important;
302
  }
303
  </style>
304
  """)
305
+
306
+ gr.Markdown("# 🚗 Sistema de Busca de Combinações de Consórcio (Automóveis)")
307
+ gr.Markdown("Preencha os campos abaixo para encontrar as melhores combinações de cotas de consórcio de veículo.")
 
308
 
309
  with gr.Row():
310
+ credito_input = gr.Number(label="Crédito Desejado (R$)", value=30000, step=1000, interactive=True)
311
+ entrada_input = gr.Number(label="Entrada Desejada (R$)", value=3000, step=100, interactive=True)
312
+ parcela_input = gr.Number(label="Parcela Desejada (R$)", value=500, step=10, interactive=True)
313
 
314
+ administradoras_options = get_administradoras_options()
315
  administradoras_multiselect = gr.CheckboxGroup(
316
  label="Filtrar por Administradoras",
317
+ choices=administradoras_options,
318
+ value=administradoras_options, # Seleciona todas por padrão
319
+ interactive=True
320
  )
321
 
322
  submit_btn = gr.Button("🔍 Buscar Combinações", variant="primary")
323
 
324
  output_text = gr.Textbox(label="Resultados da Busca", lines=20, max_lines=40, interactive=False, placeholder="Os resultados aparecerão aqui...")
325
 
326
+ # Conecta o botão à função de busca
327
  submit_btn.click(
328
  fn=ajustar_e_buscar,
329
  inputs=[credito_input, entrada_input, parcela_input, administradoras_multiselect],
330
  outputs=output_text
331
  )
332
 
333
+
334
  gr.Markdown("---")
335
+ gr.Markdown("Ferramenta desenvolvida para otimizar a busca por consórcios de automóveis.")
336
 
 
337
  if __name__ == "__main__":
338
  demo.launch() # Você não precisa disso no Colab, mas sim no arquivo que você fará o deploy