Spaces:
Sleeping
Sleeping
| # 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 | |