# app.py - VERSÃO COM MARGEM ABSOLUTA FIXA import pandas as pd import requests import io import itertools from collections import defaultdict import gradio as gr # 🔧 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 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(" ", "_") # 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") df.drop_duplicates(inplace=True) # Pré‑processa 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 formatar_moeda(valor): return f"R$ {valor:,.2f}".replace(",", "v").replace(".", ",").replace("v", ".") def calcular_tempo_parcelas(combinacao): """Calcula o tempo de parcelas baseado nas cotas da combinação.""" if not combinacao: return "" try: cotas_ordenadas = sorted(combinacao, key=lambda x: int(x.get('qtd_parcelas', 0))) prazos = sorted(set(int(c.get('qtd_parcelas', 0)) for c in combinacao)) if not prazos or prazos[0] <= 0: return "" resultado = [] prazo_anterior = 0 for prazo_atual in prazos: cotas_ativas = [c for c in cotas_ordenadas if int(c.get('qtd_parcelas', 0)) >= prazo_anterior + 1] valor_parcela_total = sum(c.get('valor_parcela', 0) for c in cotas_ativas) inicio = prazo_anterior + 1 fim = prazo_atual if inicio <= fim and valor_parcela_total > 0: resultado.append(f"{inicio}x à {fim}x: {formatar_moeda(valor_parcela_total)}/mês") prazo_anterior = prazo_atual if prazos and prazo_anterior < max(prazos): cotas_ativas_finais = [c for c in cotas_ordenadas if int(c.get('qtd_parcelas', 0)) > prazo_anterior] if cotas_ativas_finais: valor_parcela_final = sum(c.get('valor_parcela', 0) for c in cotas_ativas_finais) inicio_final = prazo_anterior + 1 fim_final = max(prazos) if valor_parcela_final > 0: resultado.append(f"{inicio_final}x à {fim_final}x: {formatar_moeda(valor_parcela_final)}/mês") return "\\n".join(resultado) if resultado else "" except Exception as e: print(f"❌ Erro ao calcular tempo de parcelas: {str(e)}") return "" def definir_margens_absolutas(credito_desejado, entrada_desejada, parcela_desejada): """Define margens fixas em R$ para crédito, entrada e parcela.""" # Margens em R$ (ex.: 500.000 → 480.000–520.000) margem_credito = 40_000 margem_entrada = 10_000 margem_parcela = 1_000 credito_min = max(0, credito_desejado - margem_credito) credito_max = credito_desejado + margem_credito entrada_min = max(0, entrada_desejada - margem_entrada) entrada_max = entrada_desejada + margem_entrada parcela_min = max(0, parcela_desejada - margem_parcela) parcela_max = parcela_desejada + margem_parcela return credito_min, credito_max, entrada_min, entrada_max, parcela_min, parcela_max def filtrar_cotas_por_faixa(cotas, credito_min, credito_max): """Filtra cotas que pelo menos têm chance de entrar na faixa de crédito.""" return [c for c in cotas if c['valor_credito'] < credito_max * 2] def buscar_combinacoes_otimizado(credito_desejado, entrada_desejada, parcela_desejada, administradoras=None, max_combinacoes=15): """Busca combinações de cotas dentro de margens fixas absolutas.""" global df_imovel, grupos_imovel if not planilha_carregada: if not carregar_planilha(): return "❌ Erro: Planilha não carregada. Por favor, tente novamente." # Filtra administradoras 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 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)}") # Usa margens fixas em R$ credito_min, credito_max, entrada_min, entrada_max, parcela_min, parcela_max = definir_margens_absolutas( credito_desejado, entrada_desejada, parcela_desejada ) print(f"💰 Valores desejados: Crédito: R$ {credito_desejado:,.2f}, Entrada: R$ {entrada_desejada:,.2f}, Parcela: R$ {parcela_desejada:,.2f}") print(f"🔍 Margem de crédito: de R$ {credito_min:,.2f} até R$ {credito_max:,.2f}") # Limites de busca max_cotas_combinacao = 15 max_combinacoes_por_adm = 4000 # para evitar explosão de combinações [web:12][web:18] resultados = [] total_combinacoes_testadas = 0 for adm, cotas in grupos_filtrados.items(): print(f"🔍 Analisando administradora: {adm} ({len(cotas)} cotas)") cotas_filtradas = filtrar_cotas_por_faixa(cotas, credito_min, credito_max) for r in range(1, min(max_cotas_combinacao + 1, len(cotas_filtradas) + 1)): combinacoes_geradas = 0 for combinacao in itertools.combinations(cotas_filtradas, 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 margens fixas 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"🔍 Total de combinações testadas: {total_combinacoes_testadas}") print(f"🔍 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]) # ordena pela menor diferença return formatar_resultados(resultados[:max_combinacoes]) def formatar_resultados(resultados): """Formata os resultados para exibição incluindo tempo de parcelas.""" 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 tempo_parcelas = calcular_tempo_parcelas(combinacao) 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}% ⏰ TEMPO DE PARCELAS: {tempo_parcelas if tempo_parcelas else "Não foi possível calcular o tempo de parcelas"} """ 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( float(credito_desejado or 0), float(entrada_desejada or 0), float(parcela_desejada or 0), administradoras, max_combinacoes=15 ) 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 [] # Interface Gradio with gr.Blocks(title="Sistema de Busca de Consórcio Imóvel") as demo: gr.HTML(""" """) 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() ) 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.") if __name__ == "__main__": demo.launch() #