Arthur-Attili's picture
Update app.py
a6940ae verified
Raw
History Blame
17.8 kB
# 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