Spaces:
Sleeping
Sleeping
| import pandas as pd | |
| import requests | |
| import io | |
| import itertools | |
| from collections import defaultdict | |
| import gradio as gr | |
| import time | |
| import random | |
| # 🔧 CONFIGURAÇÕES GLOBAIS | |
| FILE_ID = "1XvNz7DlWioT64uNdcfBSL8Ui2nRSi9gT" | |
| URL = f"https://drive.google.com/uc?export=download&id={FILE_ID}" | |
| # Variável global para armazenar o DataFrame | |
| df = None | |
| def carregar_planilha(): | |
| """Carrega e prepara a planilha para análise.""" | |
| global df | |
| try: | |
| print("⏳ Carregando planilha...") | |
| response = requests.get(URL, timeout=30) | |
| 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 | |
| colunas_necessarias = ['tipo_do_bem', 'administradora', 'valor_credito', 'valor_entrada', 'valor_parcela'] | |
| for coluna in colunas_necessarias: | |
| if coluna not in df.columns: | |
| raise ValueError(f"Coluna essencial não encontrada: {coluna}") | |
| # Remove duplicatas | |
| df.drop_duplicates(inplace=True) | |
| print("✅ Planilha carregada com sucesso") | |
| 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 formatar_moeda(valor): | |
| """Formata valor como moeda brasileira.""" | |
| try: | |
| return f"R$ {valor:,.2f}".replace(",", "v").replace(".", ",").replace("v", ".") | |
| except: | |
| return f"R$ {valor}" | |
| def buscar_combinacoes_eficiente(credito_desejado, entrada_desejada, parcela_desejada, administradoras=None): | |
| """Busca combinações de forma eficiente e garantida.""" | |
| try: | |
| print("🔍 Iniciando busca de combinações...") | |
| if df is None: | |
| print("⚠️ DataFrame não carregado, tentando carregar...") | |
| if not carregar_planilha(): | |
| return "❌ Erro: Planilha não carregada." | |
| # Debug: verifica se o DataFrame foi carregado | |
| print(f"✅ DataFrame carregado com {len(df)} linhas") | |
| print(f"📋 Colunas disponíveis: {list(df.columns)}") | |
| # Filtra por tipo de bem | |
| if 'tipo_do_bem' not in df.columns: | |
| return "❌ Coluna 'tipo_do_bem' não encontrada na planilha." | |
| df_filtrado = df[df['tipo_do_bem'].str.lower() == 'imovel'] | |
| print(f"📊 Imóveis filtrados: {len(df_filtrado)} linhas") | |
| if administradoras: | |
| if isinstance(administradoras, tuple): | |
| administradoras = list(administradoras) | |
| print(f"🎯 Administradoras selecionadas: {administradoras}") | |
| df_filtrado = df_filtrado[df_filtrado['administradora'].isin(administradoras)] | |
| print(f"📊 Após filtrar administradoras: {len(df_filtrado)} linhas") | |
| if df_filtrado.empty: | |
| return "🚫 Nenhuma cota encontrada com os filtros aplicados." | |
| # Debug: mostra algumas administradoras disponíveis | |
| adm_disponiveis = df_filtrado['administradora'].unique()[:5] | |
| print(f"🏢 Primeiras administradoras disponíveis: {adm_disponiveis}") | |
| # Tolerâncias amplas para garantir resultados | |
| credito_min, credito_max = credito_desejado * 0.80, credito_desejado * 1.20 | |
| entrada_min, entrada_max = entrada_desejada * 0.75, entrada_desejada * 1.25 | |
| parcela_min, parcela_max = parcela_desejada * 0.50, parcela_desejada * 1.50 | |
| grupos = agrupar_por_administradora(df_filtrado) | |
| print(f"📦 Grupos formados: {len(grupos)} administradoras") | |
| resultados = [] | |
| MIN_COTAS = 3 | |
| MAX_COTAS = 10 | |
| for adm, cotas in grupos.items(): | |
| print(f"🔍 Processando {adm} com {len(cotas)} cotas") | |
| if len(cotas) < MIN_COTAS: | |
| continue | |
| # Ordena por valor de crédito (otimização) | |
| cotas_ordenadas = sorted(cotas, key=lambda x: x.get('valor_credito', 0)) | |
| # Busca combinações de forma inteligente | |
| for tamanho in range(MIN_COTAS, min(MAX_COTAS + 1, len(cotas_ordenadas) + 1)): | |
| # Amostra para evitar explosão combinatória | |
| for i in range(0, len(cotas_ordenadas), max(1, len(cotas_ordenadas) // 5)): | |
| for j in range(i + tamanho, min(i + 20, len(cotas_ordenadas))): | |
| combinacao = cotas_ordenadas[i:j] | |
| if len(combinacao) != tamanho: | |
| continue | |
| try: | |
| total_credito = sum(c.get('valor_credito', 0) for c in combinacao) | |
| total_entrada_base = sum(c.get('valor_entrada', 0) for c in combinacao) | |
| total_entrada = total_entrada_base + (total_credito * 0.085) | |
| total_parcelas = sum(c.get('valor_parcela', 0) 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.get('saldo_devedor', 0) 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) >= 50: | |
| break | |
| except Exception as e: | |
| print(f"⚠️ Erro em combinação: {e}") | |
| continue | |
| if len(resultados) >= 50: | |
| break | |
| if len(resultados) >= 50: | |
| break | |
| if len(resultados) >= 50: | |
| break | |
| print(f"✅ Busca concluída: {len(resultados)} combinações encontradas") | |
| if not resultados: | |
| return "🚫 Nenhuma combinação adequada encontrada. Tente ampliar os filtros." | |
| resultados.sort(key=lambda x: x[2]) | |
| return formatar_resultados(resultados[:15]) | |
| except Exception as e: | |
| error_msg = f"❌ Erro inesperado: {str(e)}" | |
| print(error_msg) | |
| return error_msg | |
| def formatar_resultados(resultados): | |
| """Formata os resultados para exibição com diversificação por administradora.""" | |
| from collections import defaultdict | |
| if not resultados or isinstance(resultados, str): | |
| return resultados | |
| resposta = "" | |
| # 1. Agrupar os resultados por administradora, mantendo a diferença para ordenação interna | |
| grouped_by_adm = defaultdict(list) | |
| for adm, combinacao, diferenca in resultados: | |
| grouped_by_adm[adm].append((combinacao, diferenca)) | |
| # 2. Ordenar as combinações dentro de cada administradora pela diferença (do menor para o maior) | |
| for adm in grouped_by_adm: | |
| grouped_by_adm[adm].sort(key=lambda x: x[1]) # x[1] é a diferença | |
| final_resultados_diversificados = [] | |
| # Dicionário para acompanhar o índice da próxima combinação a ser pega de cada administradora | |
| adm_indices = {adm: 0 for adm in grouped_by_adm.keys()} | |
| # Lista de administradoras ativas (que ainda têm combinações disponíveis) | |
| active_adms = list(grouped_by_adm.keys()) | |
| current_adm_idx = 0 # Índice para ciclar entre as administradoras ativas | |
| # Loop para selecionar até 15 combinações, garantindo diversidade | |
| while len(final_resultados_diversificados) < 15 and active_adms: | |
| # Pega a administradora da vez para selecionar uma combinação | |
| adm_da_vez = active_adms[current_adm_idx] | |
| # Pega o índice da próxima combinação para essa administradora | |
| idx_na_adm = adm_indices[adm_da_vez] | |
| # Verifica se essa administradora ainda tem combinações disponíveis | |
| if idx_na_adm < len(grouped_by_adm[adm_da_vez]): | |
| # Pega a combinação (ignorando a diferença, que já foi usada para ordenar internamente) | |
| combinacao_data, _ = grouped_by_adm[adm_da_vez][idx_na_adm] | |
| # Adiciona à lista de resultados final (com a administradora e a combinação) | |
| final_resultados_diversificados.append((adm_da_vez, combinacao_data)) | |
| # Incrementa o índice para a próxima vez que pegarmos dessa administradora | |
| adm_indices[adm_da_vez] += 1 | |
| else: | |
| # Se essa administradora não tem mais combinações, remove ela da lista de ativas | |
| active_adms.pop(current_adm_idx) | |
| # Ajusta o current_adm_idx se a lista diminuiu e estamos no limite | |
| if current_adm_idx >= len(active_adms) and active_adms: | |
| current_adm_idx = 0 # Volta para o início da lista se removemos o último | |
| # Move para a próxima administradora no ciclo (garantindo que não estouramos o índice da lista active_adms) | |
| if active_adms: # Só avança se ainda houver administradoras ativas | |
| current_adm_idx = (current_adm_idx + 1) % len(active_adms) | |
| else: | |
| break # Não há mais administradoras ativas, sai do loop | |
| # Agora, itera sobre os resultados diversificados para formatar a resposta | |
| for idx, (adm, combinacao) in enumerate(final_resultados_diversificados, 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 | |
| # FORMATAÇÃO COMPLETA IDÊNTICA AO CARD DE AUTOMÓVEL | |
| resposta += f""" | |
| ## {adm} - Imóvel | |
| ### Crédito | |
| **{formatar_moeda(total_credito)}** | |
| **Entrada:** {formatar_moeda(total_entrada)} | |
| **Prazo:** {maior_prazo} meses | |
| **Parcela:** {formatar_moeda(total_parcelas)} | |
| **Saldo Devedor:** {formatar_moeda(total_saldo)} | |
| **Taxa Mensal:** {taxa_mensal:.2f}% | |
| **Taxa Anual:** {taxa_mensal * 12:.2f}% | |
| **Código:** {', '.join(codigos)} | |
| --- | |
| ### Ver mais detalhes ▼ | |
| **Valor Final:** {formatar_moeda(valor_final)} | |
| **Crédito Líquido:** {formatar_moeda(credito_real)} | |
| **Taxa Total:** {taxa_percentual:.2f}% ({formatar_moeda(taxa_total)}) | |
| **Nº de Cotas:** {len(combinacao)} | |
| --- | |
| """ | |
| return resposta | |
| def ajustar_e_buscar(credito_desejado, entrada_desejada, parcela_desejada, administradoras): | |
| """Função principal ajustada para a interface.""" | |
| try: | |
| if credito_desejado is None or entrada_desejada is None or parcela_desejada is None: | |
| return "⚠️ Preencha todos os valores corretamente." | |
| entrada_calculada = credito_desejado * 0.085 + entrada_desejada | |
| resultado = buscar_combinacoes_rapido( | |
| credito_desejado, entrada_calculada, parcela_desejada, administradoras | |
| ) | |
| return resultado | |
| except Exception as e: | |
| return f"❌ Erro na busca: {str(e)}" | |
| # Carrega a planilha ao iniciar | |
| carregar_planilha() | |
| # Prepara a lista de administradoras para o Gradio | |
| def get_administradoras_options(): | |
| try: | |
| if df is not None and 'administradora' in df.columns and 'tipo_do_bem' in df.columns: | |
| adm_list = df[df['tipo_do_bem'].str.lower() == 'imovel']['administradora'].unique() | |
| return sorted([adm for adm in adm_list if pd.notna(adm)]) | |
| return [] | |
| except: | |
| return [] | |
| # Interface Gradio simplificada | |
| with gr.Blocks(title="Sistema de Busca de Consórcio", theme=gr.themes.Default()) as demo: | |
| gr.Markdown("# 🏠 Busca de Combinações de Consórcio Imóvel") | |
| with gr.Row(): | |
| credito_input = gr.Number(label="Crédito Desejado (R$)", value=2000000, step=1000) | |
| entrada_input = gr.Number(label="Entrada Desejada (R$)", value=300000, step=1000) | |
| parcela_input = gr.Number(label="Parcela Desejada (R$)", value=20000, step=100) | |
| administradoras_multiselect = gr.CheckboxGroup( | |
| label="Filtrar por Administradoras", | |
| choices=get_administradoras_options(), | |
| value=get_administradoras_options() | |
| ) | |
| submit_btn = gr.Button("🔍 Buscar Combinações", variant="primary") | |
| output_text = gr.Textbox(label="Resultados da Busca", lines=20, max_lines=40) | |
| submit_btn.click( | |
| fn=ajustar_e_buscar, | |
| inputs=[credito_input, entrada_input, parcela_input, administradoras_multiselect], | |
| outputs=output_text | |
| ) | |
| # Inicia o aplicativo | |
| if __name__ == "__main__": | |
| demo.launch(server_name="0.0.0.0", server_port=7860, share=False) |