File size: 17,752 Bytes
41bb393
c3e94f7
08ba218
 
efef30d
 
c3e94f7
08ba218
e638816
efef30d
08ba218
 
 
3410ffa
e638816
c3e94f7
e638816
 
41bb393
c3e94f7
 
08ba218
41bb393
 
 
 
 
 
c3e94f7
08ba218
eeb3b83
08ba218
 
 
3410ffa
 
c3e94f7
3650fb4
08ba218
 
7f7c7ed
 
 
 
8c97de6
eeb3b83
 
8c97de6
 
 
e638816
 
 
 
7f7c7ed
41bb393
e638816
41bb393
 
8c97de6
 
 
08ba218
8c97de6
 
 
 
 
 
08ba218
8c97de6
 
a6940ae
e638816
 
 
5afc6de
8571ff6
5afc6de
 
 
e638816
41bb393
e638816
41bb393
8571ff6
 
41bb393
5afc6de
e638816
5afc6de
 
41bb393
 
76525e5
 
13bd812
5eca08e
 
 
 
41bb393
993f977
 
 
 
8571ff6
 
 
 
 
 
 
 
 
41bb393
 
 
 
 
 
5afc6de
 
76525e5
bcad208
76525e5
 
 
bcad208
76525e5
 
 
 
 
 
 
13bd812
5eca08e
13bd812
5eca08e
76525e5
bcad208
76525e5
 
bcad208
 
76525e5
 
41bb393
5afc6de
 
bcad208
5afc6de
 
41bb393
5afc6de
 
 
 
 
 
bcad208
5afc6de
 
bcad208
76525e5
 
bcad208
e638816
76525e5
bcad208
e638816
76525e5
e638816
5afc6de
76525e5
41bb393
 
5afc6de
76525e5
41bb393
 
5afc6de
76525e5
e638816
 
eeb3b83
a6940ae
76525e5
41bb393
 
 
 
 
 
 
 
76525e5
5eca08e
 
 
 
 
41bb393
993f977
 
 
 
41bb393
 
 
 
 
 
 
 
 
 
 
 
76525e5
41bb393
76525e5
 
41bb393
76525e5
 
 
5eca08e
41bb393
76525e5
 
41bb393
 
76525e5
 
41bb393
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76525e5
41bb393
76525e5
 
 
 
 
 
41bb393
76525e5
41bb393
 
 
76525e5
41bb393
 
 
 
76525e5
 
 
 
 
 
 
543325c
76525e5
 
a7992d0
 
 
 
 
 
 
 
 
 
 
 
 
 
76525e5
 
eeb3b83
41bb393
76525e5
 
eeb3b83
76525e5
eeb3b83
 
 
 
76525e5
 
a7992d0
76525e5
8c97de6
eeb3b83
efef30d
9f2d369
eeb3b83
0b7f02a
a6940ae
0b7f02a
eeb3b83
7f7c7ed
8c97de6
41bb393
eeb3b83
 
 
 
9bf522d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
# app.py - VERSÃO CORRIGIDA E OTIMIZADA
import pandas as pd
import requests
import io
import itertools
from collections import defaultdict
import gradio as gr
import time
import math

# 🔧 CONFIGURAÇÕES GLOBAIS
FILE_ID = "1XvNz7DlWioT64uNdcfBSL8Ui2nRSi9gT"
URL = f"https://drive.google.com/uc?export=download&id={FILE_ID}"

# Variáveis globais para armazenamento
df = None
df_imovel = None
grupos_imovel = None
planilha_carregada = False

def carregar_planilha():
    """Carrega e prepara a planilha para análise."""
    global df, df_imovel, grupos_imovel, planilha_carregada
    
    # Se já carregou, não precisa carregar novamente
    if planilha_carregada:
        return True
        
    try:
        print("⏳ Carregando planilha...")
        response = requests.get(URL)
        response.raise_for_status()
        data = io.BytesIO(response.content)
        df = pd.read_excel(data)

        # Padroniza nomes das colunas
        df.columns = df.columns.str.strip().str.lower().str.replace(" ", "_")

        # Converte colunas numéricas
        colunas_numericas = ['valor_credito', 'valor_entrada', 'valor_parcela', 'saldo_devedor', 'qtd_parcelas']
        for coluna in colunas_numericas:
            if coluna in df.columns:
                df[coluna] = pd.to_numeric(df[coluna], errors='coerce').fillna(0)

        # Verifica colunas essenciais
        if 'tipo_do_bem' not in df.columns or 'administradora' not in df.columns:
            raise ValueError("Colunas essenciais não encontradas")

        # Remove duplicatas
        df.drop_duplicates(inplace=True)
        
        # Pré-filtra e pré-processa dados de imóvel
        df_imovel = df[df['tipo_do_bem'].str.lower() == 'imovel'].copy()
        grupos_imovel = agrupar_por_administradora(df_imovel)

        planilha_carregada = True
        print("✅ Planilha carregada e pré-processada com sucesso")
        print(f"✅ Total de cotas de imóvel: {len(df_imovel)}")
        print(f"✅ Administradoras disponíveis: {list(grupos_imovel.keys())}")
        return True

    except Exception as e:
        print(f"❌ Erro ao carregar planilha: {str(e)}")
        return False

def agrupar_por_administradora(dataframe):
    """Agrupa cotas por administradora."""
    grupos = defaultdict(list)
    for _, row in dataframe.iterrows():
        grupos[row['administradora']].append(row.to_dict())
    return grupos

def buscar_combinacoes_otimizado(credito_desejado, entrada_desejada, parcela_desejada, administradoras=None, max_combinacoes=15):
    """Busca combinações de cotas dentro dos parâmetros especificados de forma otimizada."""
    global df_imovel, grupos_imovel
    
    # Garante que o DataFrame está carregado
    if not planilha_carregada:
        if not carregar_planilha():
            return "❌ Erro: Planilha não carregada. Por favor, tente novamente."

    # Filtra administradoras se especificado
    if administradoras and len(administradoras) > 0:
        grupos_filtrados = {adm: cotas for adm, cotas in grupos_imovel.items() if adm in administradoras}
        print(f"🔍 Filtrando por administradoras: {administradoras}")
    else:
        grupos_filtrados = grupos_imovel
        print("🔍 Buscando em todas as administradoras")

    if not grupos_filtrados:
        return "🚫 Nenhuma cota encontrada com os filtros aplicados. Tente ajustar os parâmetros."

    print(f"🔍 Total de administradoras no filtro: {len(grupos_filtrados)}")
    
    # TOLERÂNCIAS - Ajustadas para valores altos
    # Para valores muito altos, aumentamos as tolerâncias
    if credito_desejado > 10000000:  # Para valores acima de 10 milhões
        tolerancia_credito = 0.50  # 50%
        tolerancia_entrada = 0.50  # 50%
        tolerancia_parcela = 1.00  # 100%
        max_cotas_combinacao = 25  # Até 25 cotas por combinação
    else:
        tolerancia_credito = 0.05  # 05%
        tolerancia_entrada = 0.05  # 05%
        tolerancia_parcela = 0.25  # 25%
        max_cotas_combinacao = 15  # Até 15 cotas por combinação

    # Define faixas de tolerância
    credito_min = credito_desejado * (1 - tolerancia_credito)
    credito_max = credito_desejado * (1 + tolerancia_credito)
    entrada_min = entrada_desejada * (1 - tolerancia_entrada)
    entrada_max = entrada_desejada * (1 + tolerancia_entrada)
    parcela_min = parcela_desejada * (1 - tolerancia_parcela)
    parcela_max = parcela_desejada * (1 + tolerancia_parcela)

    print(f"💰 Valores desejados: Crédito: R$ {credito_desejado:,.2f}, Entrada: R$ {entrada_desejada:,.2f}, Parcela: R$ {parcela_desejada:,.2f}")
    print(f"🔧 Tolerâncias: Crédito: ±{tolerancia_credito*100:.1f}%, Entrada: ±{tolerancia_entrada*100:.1f}%, Parcela: ±{tolerancia_parcela*100:.1f}%")
    print(f"🔧 Faixas aceitáveis: Crédito: R$ {credito_min:,.2f} - R$ {credito_max:,.2f}")
    print(f"🔧 Faixas aceitáveis: Entrada: R$ {entrada_min:,.2f} - R$ {entrada_max:,.2f}")
    print(f"🔧 Faixas aceitáveis: Parcela: R$ {parcela_min:,.2f} - R$ {parcela_max:,.2f}")
    print(f"🔧 Máximo de cotas por combinação: {max_cotas_combinacao}")

    resultados = []
    total_combinacoes_testadas = 0
    
    # Para cada administradora, busca combinações
    for adm, cotas in grupos_filtrados.items():
        print(f"🔍 Analisando administradora: {adm} ({len(cotas)} cotas)")
        
        # Para valores altos, NÃO filtramos cotas individuais primeiro
        # Isso permite que cotas menores sejam combinadas para atingir valores altos
        cotas_para_combinar = cotas
        
        # Gera combinações
        for r in range(1, min(max_cotas_combinacao + 1, len(cotas_para_combinar) + 1)):
            # Limita o número de combinações testadas por administradora
            if credito_desejado > 10000000:
                max_combinacoes_por_adm = 7000
            else:
                max_combinacoes_por_adm = 3000
            combinacoes_geradas = 0
            
            for combinacao in itertools.combinations(cotas_para_combinar, r):
                if combinacoes_geradas >= max_combinacoes_por_adm:
                    break
                    
                total_combinacoes_testadas += 1
                combinacoes_geradas += 1
                
                total_credito = sum(c['valor_credito'] for c in combinacao)
                total_entrada_base = sum(c['valor_entrada'] for c in combinacao)
                total_entrada = total_entrada_base + (total_credito * 0.085)
                total_parcelas = sum(c['valor_parcela'] for c in combinacao)

                # Verifica se a combinação está dentro das tolerâncias
                if (credito_min <= total_credito <= credito_max and
                    entrada_min <= total_entrada <= entrada_max and
                    parcela_min <= total_parcelas <= parcela_max):

                    total_saldo = sum(c['saldo_devedor'] for c in combinacao)
                    valor_final = round(total_entrada + total_saldo, 2)
                    diferenca = abs(valor_final - total_credito)

                    resultados.append((adm, combinacao, diferenca))
                    
                    # Para se já encontrou combinações suficientes
                    if len(resultados) >= max_combinacoes * 3:
                        break
            
            if len(resultados) >= max_combinacoes * 3:
                break
        
        if len(resultados) >= max_combinacoes * 3:
            break

    print(f"🔍 Total de combinações testadas: {total_combinacoes_testadas}")
    print(f"🔍 Combinações válidas encontradas: {len(resultados)}")

    if not resultados:
        # Tenta uma busca mais ampla se não encontrou resultados
        print("🔍 Nenhuma combinação encontrada. Tentando busca ampla...")
        return buscar_combinacoes_amplo(credito_desejado, entrada_desejada, parcela_desejada, administradoras, max_combinacoes)

    # Ordena pela diferença e pega as melhores
    resultados.sort(key=lambda x: x[2])
    return formatar_resultados(resultados[:max_combinacoes])

def buscar_combinacoes_amplo(credito_desejado, entrada_desejada, parcela_desejada, administradoras=None, max_combinacoes=15):
    """Busca combinações com tolerâncias mais amplas para valores altos."""
    global grupos_imovel
    
    # Filtra administradoras se especificado
    if administradoras and len(administradoras) > 0:
        grupos_filtrados = {adm: cotas for adm, cotas in grupos_imovel.items() if adm in administradoras}
    else:
        grupos_filtrados = grupos_imovel

    # Tolerâncias mais amplas para valores altos
    if credito_desejado > 10000000: # Para valores acima de 10 milhões
        tolerancia_credito = 0.50  # 50%
        tolerancia_entrada = 0.50  # 50%
        tolerancia_parcela = 1.00  # 100%
        max_cotas_combinacao = 25  # Até 25 cotas por combinação
    else:
        tolerancia_credito = 0.05  # 05%
        tolerancia_entrada = 0.05  # 05%
        tolerancia_parcela = 0.25  # 25%
        max_cotas_combinacao = 15  # Até 15 cotas por combinação

    credito_min = credito_desejado * (1 - tolerancia_credito)
    credito_max = credito_desejado * (1 + tolerancia_credito)
    entrada_min = entrada_desejada * (1 - tolerancia_entrada)
    entrada_max = entrada_desejada * (1 + tolerancia_entrada)
    parcela_min = parcela_desejada * (1 - tolerancia_parcela)
    parcela_max = parcela_desejada * (1 + tolerancia_parcela)

    print(f"🔍 BUSCA AMPLA: Tolerâncias: Crédito: ±{tolerancia_credito*100:.1f}%, Entrada: ±{tolerancia_entrada*100:.1f}%, Parcela: ±{tolerancia_parcela*100:.1f}%")
    print(f"🔍 BUSCA AMPLA: Máximo de cotas por combinação: {max_cotas_combinacao}")

    resultados = []
    total_combinacoes_testadas = 0
    
    for adm, cotas in grupos_filtrados.items():
        print(f"🔍 BUSCA AMPLA - Analisando administradora: {adm}")
        
        # Gera combinações
        for r in range(1, min(max_cotas_combinacao + 1, len(cotas) + 1)):
            combinacoes_geradas = 0
            max_combinacoes_por_adm = 7000
            
            for combinacao in itertools.combinations(cotas, r):
                if combinacoes_geradas >= max_combinacoes_por_adm:
                    break
                    
                total_combinacoes_testadas += 1
                combinacoes_geradas += 1
                
                total_credito = sum(c['valor_credito'] for c in combinacao)
                total_entrada_base = sum(c['valor_entrada'] for c in combinacao)
                total_entrada = total_entrada_base + (total_credito * 0.085)
                total_parcelas = sum(c['valor_parcela'] for c in combinacao)

                if (credito_min <= total_credito <= credito_max and
                    entrada_min <= total_entrada <= entrada_max and
                    parcela_min <= total_parcelas <= parcela_max):

                    total_saldo = sum(c['saldo_devedor'] for c in combinacao)
                    valor_final = round(total_entrada + total_saldo, 2)
                    diferenca = abs(valor_final - total_credito)

                    resultados.append((adm, combinacao, diferenca))
                    
                    if len(resultados) >= max_combinacoes * 2:
                        break
            
            if len(resultados) >= max_combinacoes * 2:
                break
        
        if len(resultados) >= max_combinacoes * 2:
            break

    print(f"🔍 BUSCA AMPLA: Total de combinações testadas: {total_combinacoes_testadas}")
    print(f"🔍 BUSCA AMPLA: Combinações válidas encontradas: {len(resultados)}")

    if not resultados:
        return "🚫 Nenhuma combinação adequada encontrada com os parâmetros fornecidos."

    resultados.sort(key=lambda x: x[2])
    return formatar_resultados(resultados[:max_combinacoes])

def formatar_moeda(valor):
    return f"R$ {valor:,.2f}".replace(",", "v").replace(".", ",").replace("v", ".")

def formatar_resultados(resultados):
    """Formata os resultados para exibição."""
    if not resultados:
        return "🚫 Nenhuma combinação adequada encontrada com os parâmetros fornecidos."
    
    resposta = ""
    for idx, (adm, combinacao, _) in enumerate(resultados, 1):
        codigos = [str(c.get('codigo', 'N/A')) for c in combinacao]
        total_credito = sum(c['valor_credito'] for c in combinacao)
        total_entrada_base = sum(c['valor_entrada'] for c in combinacao)
        total_entrada = total_entrada_base + (total_credito * 0.085)
        total_saldo = sum(c['saldo_devedor'] for c in combinacao)
        total_parcelas = sum(c['valor_parcela'] for c in combinacao)
        maior_prazo = max(int(c.get('qtd_parcelas', 0)) for c in combinacao)

        valor_final = round(total_entrada + total_saldo, 2)
        credito_real = round(total_credito - total_entrada, 2)
        taxa_total = total_saldo - credito_real
        taxa_percentual = (taxa_total / credito_real) * 100 if credito_real > 0 else 0
        taxa_mensal = taxa_percentual / maior_prazo if maior_prazo > 0 else 0

        resposta += f"""
--- COMBINAÇÃO {idx} ---
🏢 Administradora: {adm}
🔢 Códigos: {', '.join(codigos)} ({len(combinacao)} cotas)
💰 Crédito Total: {formatar_moeda(total_credito)}
💵 Entrada (+comissão): {formatar_moeda(total_entrada)}
📉 Saldo Devedor: {formatar_moeda(total_saldo)}
💲 Parcela: {formatar_moeda(total_parcelas)}
⏳ Prazo: {maior_prazo} meses
🔚 Valor Final: {formatar_moeda(valor_final)}
💠 Crédito Líquido: {formatar_moeda(credito_real)}
📊 Taxa Total: {taxa_percentual:.2f}% ({formatar_moeda(taxa_total)})
🗓️ Taxa Mensal: {taxa_mensal:.2f}%
🗓️ Taxa Anual: {taxa_mensal * 12:.2f}%
"""
    return resposta

# Carrega a planilha uma vez quando o aplicativo inicia
carregar_planilha()

def ajustar_e_buscar(credito_desejado, entrada_desejada, parcela_desejada, administradoras):
    return buscar_combinacoes_otimizado(
        credito_desejado, entrada_desejada, parcela_desejada, administradoras, max_combinacoes=15
    )

# Prepara a lista de administradoras para o Gradio
def get_administradoras_options():
    if df is not None and planilha_carregada:
        adm_list = df[df['tipo_do_bem'].str.lower() == 'imovel']['administradora'].unique()
        return sorted(adm_list)
    return []

# Criando a interface Gradio
with gr.Blocks(title="Sistema de Busca de Consórcio Imóvel") as demo:
    gr.HTML("""
    <link href="https://fonts.googleapis.com/css2?family=Poppins&display=swap" rel="stylesheet">
    <style>
        * {
            font-family: 'Poppins', sans-serif !important;
        }
    
        body {
            background-color: #ff8f00 !important;
            color: black !important;
        }
    
        /* Inputs Numéricos: Crédito, Entrada, Parcela */
        input[type="number"] {
            background-color: white !important;
            border: 2px solid #000 !important;
            border-radius: 12px !important;
            color: black !important;
            padding: 10px !important;
            font-weight: 500;
        }
    
        /* Inputs de texto e áreas de texto (como a saída e código) */
        input[type="text"], textarea {
            background-color: white !important;
            border: 2px solid black !important;
            border-radius: 12px !important;
            color: black !important;
            padding: 10px !important;
        }
    
        input::placeholder, textarea::placeholder {
            color: #333 !important;
            opacity: 0.7;
        }
    
        .gr-checkbox label {
            display: flex;
            align-items: center;
            gap: 8px;
            font-weight: 600;
        }
    
        input[type="checkbox"] {
            accent-color: black !important;
            width: 18px;
            height: 18px;
        }
    
        .gr-button {
            font-weight: bold;
            border-radius: 10px !important;
            background-color: white !important;
            color: black !important;
            border: 2px solid black !important;
        }
    
        .gr-button:hover {
            background-color: #fff3e0 !important;
            color: black !important;
        }
    
        label {
            font-weight: 600 !important;
        }
    </style>
    """)


    gr.Markdown("# 🏠 Busca de Combinações de Consórcio Imóvel")
    gr.Markdown("Insira os valores desejados para crédito, entrada e parcela, e filtre por administradora.")

    with gr.Row():
        credito_input = gr.Number(label="Crédito Desejado (R$)", value=300000, step=1000)
        entrada_input = gr.Number(label="Entrada Desejada (R$)", value=30000, step=1000)
        parcela_input = gr.Number(label="Parcela Desejada (R$)", value=1500, step=100)

    administradoras_multiselect = gr.CheckboxGroup(
        label="Filtrar por Administradoras",
        choices=get_administradoras_options(),
        value=get_administradoras_options() # Seleciona todas por padrão
    )

    submit_btn = gr.Button("🔍 Buscar Combinações", variant="primary")

    output_text = gr.Textbox(label="Resultados da Busca", lines=20, max_lines=40, interactive=False, placeholder="Os resultados aparecerão aqui...")

    submit_btn.click(
        fn=ajustar_e_buscar,
        inputs=[credito_input, entrada_input, parcela_input, administradoras_multiselect],
        outputs=output_text
    )

    gr.Markdown("---")
    gr.Markdown("Desenvolvido para auxiliar na busca por cotas de consórcio ideais.")

# Inicia o aplicativo Gradio
if __name__ == "__main__":
    demo.launch() # Você não precisa disso no Colab, mas sim no arquivo que você fará o deploy