Spaces:
Sleeping
Sleeping
| # 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(""" | |
| <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; } | |
| input[type="number"], input[type="text"], textarea { | |
| background-color: white !important; | |
| border: 2px solid black !important; | |
| border-radius: 12px !important; | |
| color: black !important; | |
| padding: 10px !important; | |
| } | |
| .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; } | |
| </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() | |
| ) | |
| 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() | |
| # |