Arthur-Attili commited on
Commit
becdb30
·
verified ·
1 Parent(s): 2366eaf

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +202 -304
app.py CHANGED
@@ -1,312 +1,210 @@
1
  import os
2
- os.environ["GRADIO_ANALYTICS_ENABLED"] = "False"
3
- os.environ["HF_HUB_DISABLE_TELEMETRY"] = "1"
4
-
5
- import pandas as pd
6
  import requests
7
- import io
8
- import itertools
9
- from collections import defaultdict
10
- import gradio as gr
11
-
12
- # 🔧 CONFIGURAÇÕES DO ARQUIVO
13
- FILE_ID = "1EoH8V3Fyls0mNa95VcBN6ZL1B552ty3X"
14
- URL = f"https://drive.google.com/uc?export=download&id={FILE_ID}"
15
-
16
- # Variáveis globais
17
- df_auto = None
18
- grupos_auto = None
19
- planilha_carregada = False
20
- ADMIN_OPCOES = []
21
-
22
-
23
- def carregar_planilha():
24
- global df_auto, grupos_auto, planilha_carregada, ADMIN_OPCOES
25
-
26
- if planilha_carregada:
27
- return True
28
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
  try:
30
- print("⏳ Carregando planilha de automóveis...")
31
- response = requests.get(URL)
32
- response.raise_for_status()
33
- data = io.BytesIO(response.content)
34
- df = pd.read_excel(data)
35
-
36
- df.columns = df.columns.str.strip().str.lower().str.replace(" ", "_")
37
-
38
- colunas_numericas = ['valor_credito', 'valor_entrada', 'valor_parcela', 'saldo_devedor', 'qtd_parcelas']
39
- for col in colunas_numericas:
40
- if col in df.columns:
41
- df[col] = pd.to_numeric(df[col], errors='coerce').fillna(0)
42
-
43
- if 'tipo_do_bem' not in df.columns or 'administradora' not in df.columns:
44
- raise ValueError("Colunas essenciais não encontradas")
45
-
46
- df.drop_duplicates(inplace=True)
47
-
48
- df_auto = df[df['tipo_do_bem'].str.lower() == 'auto'].copy()
49
- df_auto['administradora'] = df_auto['administradora'].astype(str).str.strip().str.lower()
50
- df_auto = df_auto[df_auto['administradora'].notna() & (df_auto['administradora'] != '')]
51
-
52
- grupos_auto = defaultdict(list)
53
- for _, row in df_auto.iterrows():
54
- grupos_auto[row['administradora']].append(row.to_dict())
55
-
56
- ADMIN_OPCOES = sorted(df_auto['administradora'].unique())
57
- planilha_carregada = True
58
-
59
- print(f"✅ Total de cotas AUTO: {len(df_auto)}")
60
- print(f"✅ Administradoras AUTO: {ADMIN_OPCOES}")
61
- return True
62
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
  except Exception as e:
64
- print(f"Erro ao carregar planilha AUTO: {e}")
65
- return False
66
-
67
-
68
- def formatar_moeda(valor):
69
- return f"R$ {valor:,.2f}".replace(",", "v").replace(".", ",").replace("v", ".")
70
-
71
-
72
- def definir_margens_absolutas(credito, entrada, parcela):
73
- margem_credito = 20_000 # ex.: 30.000 → 10.000–50.000
74
- margem_entrada = 2_000
75
- margem_parcela = 100
76
-
77
- cred_min = max(0, credito - margem_credito)
78
- cred_max = credito + margem_credito
79
- ent_min = max(0, entrada - margem_entrada)
80
- ent_max = entrada + margem_entrada
81
- parc_min = max(0, parcela - margem_parcela)
82
- parc_max = parcela + margem_parcela
83
-
84
- return cred_min, cred_max, ent_min, ent_max, parc_min, parc_max
85
-
86
-
87
- def filtrar_cotas_por_faixa(cotas, cred_min, cred_max):
88
- return [c for c in cotas if c['valor_credito'] < cred_max * 2]
89
-
90
-
91
- def calcular_tempo_parcelas(combinacao):
92
- if not combinacao:
93
- return ""
94
 
 
 
 
95
  try:
96
- cotas_ordenadas = sorted(combinacao, key=lambda x: int(x.get('qtd_parcelas', 0)))
97
- prazos = sorted(set(int(c.get('qtd_parcelas', 0)) for c in combinacao))
98
-
99
- if not prazos or prazos[0] <= 0:
100
- return ""
101
-
102
- resultado = []
103
- prazo_anterior = 0
104
-
105
- for prazo_atual in prazos:
106
- cotas_ativas = [c for c in cotas_ordenadas if int(c.get('qtd_parcelas', 0)) >= prazo_anterior + 1]
107
- valor_parcela_total = sum(c.get('valor_parcela', 0) for c in cotas_ativas)
108
-
109
- inicio = prazo_anterior + 1
110
- fim = prazo_atual
111
-
112
- if inicio <= fim and valor_parcela_total > 0:
113
- resultado.append(f"{inicio}x à {fim}x: {formatar_moeda(valor_parcela_total)}/mês")
114
-
115
- prazo_anterior = prazo_atual
116
-
117
- if prazos and prazo_anterior < max(prazos):
118
- cotas_ativas_finais = [c for c in cotas_ordenadas if int(c.get('qtd_parcelas', 0)) > prazo_anterior]
119
- if cotas_ativas_finais:
120
- valor_parcela_final = sum(c.get('valor_parcela', 0) for c in cotas_ativas_finais)
121
- inicio_final = prazo_anterior + 1
122
- fim_final = max(prazos)
123
- if valor_parcela_final > 0:
124
- resultado.append(f"{inicio_final}x à {fim_final}x: {formatar_moeda(valor_parcela_final)}/mês")
125
-
126
- return "\\n".join(resultado) if resultado else ""
127
-
128
  except Exception as e:
129
- print(f"❌ Erro calcular tempo de parcelas AUTO: {e}")
130
- return ""
131
 
132
-
133
- def buscar_combinacoes(credito, entrada, parcela, adm_selecionadas):
134
- if not planilha_carregada:
135
- if not carregar_planilha():
136
- return "❌ Erro: planilha de automóveis não carregada."
137
-
138
- # Filtra administradoras
139
- if adm_selecionadas:
140
- grupos = {adm: cotas for adm, cotas in grupos_auto.items() if adm in adm_selecionadas}
141
- else:
142
- grupos = grupos_auto
143
-
144
- if not grupos:
145
- return "🚫 Nenhuma cota de automóvel encontrada com os filtros."
146
-
147
- # Margens absolutas
148
- cred_min, cred_max, ent_min, ent_max, parc_min, parc_max = definir_margens_absolutas(
149
- float(credito or 0),
150
- float(entrada or 0),
151
- float(parcela or 0)
152
- )
153
-
154
- print(f"💰 AUTO - Crédito: R$ {credito:,.2f} → [{cred_min:,.2f}, {cred_max:,.2f}]")
155
-
156
- # Limites de busca
157
- max_cotas_combinacao = 15
158
- max_combinacoes_por_adm = 4000
159
-
160
- resultados = []
161
- total_combinacoes_testadas = 0
162
-
163
- for adm, cotas in grupos.items():
164
- print(f"🔍 AUTO - Administradora: {adm} ({len(cotas)} cotas)")
165
- cotas_filtradas = filtrar_cotas_por_faixa(cotas, cred_min, cred_max)
166
-
167
- for r in range(1, min(max_cotas_combinacao + 1, len(cotas_filtradas) + 1)):
168
- combinacoes_geradas = 0
169
- for combinacao in itertools.combinations(cotas_filtradas, r):
170
- if combinacoes_geradas >= max_combinacoes_por_adm:
171
- break
172
-
173
- total_combinacoes_testadas += 1
174
- combinacoes_geradas += 1
175
-
176
- total_cred = sum(c['valor_credito'] for c in combinacao)
177
- total_ent_base = sum(c['valor_entrada'] for c in combinacao)
178
- total_ent = total_ent_base + total_cred * 0.085
179
- total_parc = sum(c['valor_parcela'] for c in combinacao)
180
-
181
- if (cred_min <= total_cred <= cred_max and
182
- ent_min <= total_ent <= ent_max and
183
- parc_min <= total_parc <= parc_max):
184
-
185
- total_saldo = sum(c['saldo_devedor'] for c in combinacao)
186
- valor_final = round(total_ent + total_saldo, 2)
187
- diff = abs(valor_final - total_cred)
188
- resultados.append((adm, combinacao, diff))
189
-
190
- if len(resultados) >= 45:
191
- break
192
- if len(resultados) >= 45:
193
- break
194
- if len(resultados) >= 45:
195
- break
196
-
197
- print(f"🔍 AUTO - Total de combinações testadas: {total_combinacoes_testadas}")
198
-
199
- if not resultados:
200
- return "🚫 Nenhuma combinação de auto encontrada com os parâmetros fornecidos."
201
-
202
- resultados.sort(key=lambda x: x[2])
203
- return formatar_resultados(resultados[:15])
204
-
205
-
206
- def formatar_resultados(resultados):
207
- if not resultados:
208
- return "🚫 Nenhuma combinação de auto adequada encontrada."
209
-
210
- resposta = ""
211
- for idx, (adm, comb, _) in enumerate(resultados, 1):
212
- codigos = [str(c.get('codigo', 'N/A')) for c in comb]
213
- total_cred = sum(c['valor_credito'] for c in comb)
214
- total_ent_base = sum(c['valor_entrada'] for c in comb)
215
- total_ent = total_ent_base + total_cred * 0.085
216
- total_saldo = sum(c['saldo_devedor'] for c in comb)
217
- total_parc = sum(c['valor_parcela'] for c in comb)
218
- maior_prazo = max(int(c.get('qtd_parcelas', 0)) for c in comb)
219
- valor_final = round(total_ent + total_saldo, 2)
220
- cred_real = total_cred - total_ent
221
- taxa_total = total_saldo - cred_real
222
- taxa_perc = (taxa_total / cred_real) * 100 if cred_real > 0 else 0
223
- taxa_mensal = taxa_perc / maior_prazo if maior_prazo > 0 else 0
224
- tempo = calcular_tempo_parcelas(comb)
225
-
226
- resposta += f"""
227
- --- COMBINAÇÃO {idx} AUTO ---
228
- 🏢 Administradora: {adm}
229
- 🔢 Códigos: {', '.join(codigos)} ({len(comb)} cotas)
230
- 💰 Crédito Total: {formatar_moeda(total_cred)}
231
- 💵 Entrada (+comissão): {formatar_moeda(total_ent)}
232
- 📉 Saldo Devedor: {formatar_moeda(total_saldo)}
233
- 💲 Parcela: {formatar_moeda(total_parc)}
234
- ⏳ Prazo: {maior_prazo} meses
235
- 🔚 Valor Final: {formatar_moeda(valor_final)}
236
- 💠 Crédito Líquido: {formatar_moeda(cred_real)}
237
- 📊 Taxa Total: {taxa_perc:.2f}% ({formatar_moeda(taxa_total)})
238
- 🗓️ Taxa Mensal: {taxa_mensal:.2f}%
239
- 🗓️ Taxa Anual: {taxa_mensal * 12:.2f}%
240
- ⏰ TEMPO DE PARCELAS:
241
- {tempo if tempo else "Não foi possível calcular"}
242
- """
243
- return resposta
244
-
245
-
246
- # 🔁 Carregar a planilha uma vez, no início do programa
247
- carregar_planilha()
248
-
249
-
250
- # Interface Gradio (exatamente igual ao imóvel, com Blocks)
251
- with gr.Blocks(title="🚗 Sistema de Busca de Consórcio Auto") as demo_auto:
252
- gr.HTML("""
253
- <link href="https://fonts.googleapis.com/css2?family=Poppins&display=swap" rel="stylesheet">
254
- <style>
255
- * { font-family: 'Poppins', sans-serif !important; }
256
- body { background-color: #ff8f00 !important; color: black !important; }
257
- input[type="number"], textarea {
258
- background-color: white !important;
259
- border: 2px solid black !important;
260
- border-radius: 12px !important;
261
- color: black !important;
262
- padding: 10px !important;
263
- }
264
- .gr-button {
265
- font-weight: bold;
266
- border-radius: 10px !important;
267
- background-color: white !important;
268
- color: black !important;
269
- border: 2px solid black !important;
270
- }
271
- </style>
272
- """)
273
-
274
- gr.Markdown("# 🚗 Busca de Combinações de Consórcio Auto")
275
- gr.Markdown("Insira os valores desejados para crédito, entrada e parcela, e filtre por administradora.")
276
-
277
- with gr.Row():
278
- credito_input = gr.Number(label="Crédito Desejado (R$)", value=30000, step=1000)
279
- entrada_input = gr.Number(label="Entrada Desejada (R$)", value=3000, step=100)
280
- parcela_input = gr.Number(label="Parcela Desejada (R$)", value=500, step=10)
281
-
282
- adm_input = gr.Dropdown(
283
- choices=ADMIN_OPCOES,
284
- label="Filtrar por Administradoras",
285
- multiselect=True,
286
- value=ADMIN_OPCOES,
287
- interactive=True
288
- )
289
-
290
- submit_btn = gr.Button("🔍 Buscar Combinações AUTO", variant="primary")
291
- output_text = gr.Textbox(
292
- label="Resultados AUTO",
293
- lines=20,
294
- max_lines=40,
295
- interactive=False,
296
- placeholder="Os resultados aparecerão aqui..."
297
- )
298
-
299
- submit_btn.click(
300
- fn=buscar_combinacoes,
301
- inputs=[credito_input, entrada_input, parcela_input, adm_input],
302
- outputs=output_text
303
- )
304
-
305
- gr.Markdown("---")
306
- gr.Markdown("Desenvolvido para auxiliar na busca por cotas de consórcio automotivo.")
307
-
308
-
309
- # Executar o app
310
- if __name__ == "__main__":
311
- demo_auto.launch()
312
- #
 
1
  import os
 
 
 
 
2
  import requests
3
+ import json
4
+ from flask import Flask, request, jsonify
5
+ from flask_cors import CORS
6
+ from functools import lru_cache
7
+ from datetime import datetime
8
+ import logging
9
+
10
+ # ==================== CONFIGURAÇÃO ====================
11
+ logging.basicConfig(level=logging.DEBUG)
12
+ logger = logging.getLogger(__name__)
13
+
14
+ app = Flask(__name__)
15
+ CORS(app, origins=["https://www.maxconconsorcios.com.br", "https://vendasuacota.com.br"])
16
+
17
+ # ==================== CONFIGURAÇÃO DA API REAL ====================
18
+ API_BASE_URL = "https://api.maxconconsorcios.com.br"
19
+ WHATSAPP_NUMBER = "5516997865738"
20
+
21
+ # ==================== FUNÇÕES AUXILIARES ====================
22
+ def format_currency(value):
23
+ """Formata valor para moeda brasileira"""
24
+ if value is None:
25
+ return "R$ 0,00"
26
+ return f"R$ {float(value):,.2f}".replace(",", "v").replace(".", ",").replace("v", ".")
27
+
28
+ def calcular_entrada_total(cota):
29
+ """Calcula entrada total incluindo comissão de 8.5%"""
30
+ credito = float(cota.get('valor_credito', 0))
31
+ entrada = float(cota.get('entrada', 0))
32
+ comissao = credito * 0.085
33
+ return entrada + comissao
34
+
35
+ def get_administradora_nome(admin_id):
36
+ """Mapeia ID da administradora para nome"""
37
+ admin_map = {
38
+ 1:'Itaú', 2:'Bradesco', 3:'Santander', 4:'Honda', 5:'Volkswagen',
39
+ 6:'Serello', 7:'Ademicon', 8:'Remaza', 9:'Porto Seguro VP',
40
+ 10:'Banrisul', 11:'Embracon', 12:'Serrana', 13:'Caixa XS5',
41
+ 14:'Caixa CNP', 15:'Unifisa', 16:'HS Consorcios', 17:'Caixa Seguradora',
42
+ 18:'Porto Seguro', 19:'Mycon', 20:'Rodobens', 21:'Ford',
43
+ 22:'Sicoob Unicoob', 23:'Zema', 24:'Maggi', 25:'Ancora', 26:'Yamaha',
44
+ 27:'Sponchiado', 28:'Scania', 29:'Disal', 30:'Servopa', 31:'Canopus',
45
+ 32:'Bradesco', 33:'Banco do Brasil', 34:'Santander', 35:'Sicredi'
46
+ }
47
+ return admin_map.get(admin_id, 'Não especificado')
48
+
49
+ # ==================== ROTA PRINCIPAL ====================
50
+ @app.route('/api/buscar_combinacoes', methods=['POST'])
51
+ def buscar_combinacoes():
52
+ """
53
+ Endpoint que busca combinações de cotas usando a API real
54
+ (mesmo backend da Foto 1)
55
+ """
56
  try:
57
+ data = request.get_json()
58
+
59
+ produto = data.get('produto', 'moto')
60
+ credito_min = float(data.get('credito', 0))
61
+ entrada_max = float(data.get('entrada', 0))
62
+ parcela_max = float(data.get('parcela', 0))
63
+ administradoras = data.get('administradoras', [])
64
+
65
+ logger.info(f"Buscando combinações para {produto} com crédito mínimo R$ {credito_min:,.2f}")
66
+
67
+ # Mapeamento de categorias
68
+ categoria = 'veiculo' if produto in ['moto', 'carro', 'caminhao'] else 'imovel'
69
+
70
+ # Busca cotas na API real
71
+ response = requests.post(
72
+ f"{API_BASE_URL}/api/cotas",
73
+ json={
74
+ 'categoria': categoria,
75
+ 'valor_credito_min': credito_min if credito_min > 0 else None,
76
+ 'valor_parcela_max': parcela_max if parcela_max > 0 else None
77
+ }
78
+ )
79
+
80
+ if response.status_code != 200:
81
+ return jsonify({'error': 'Erro ao buscar cotas'}), 500
82
+
83
+ cotas = response.json()
84
+
85
+ # Filtra por administradora
86
+ if administradoras:
87
+ admin_ids = []
88
+ for admin_nome in administradoras:
89
+ for admin_id, nome in get_admin_map().items():
90
+ if nome.lower() == admin_nome.lower():
91
+ admin_ids.append(admin_id)
92
+ break
93
+
94
+ if admin_ids:
95
+ cotas = [c for c in cotas if c.get('administradora_id') in admin_ids]
96
+
97
+ # Filtra disponíveis
98
+ cotas = [c for c in cotas if c.get('reserva') != 'reservado']
99
+
100
+ # Filtra por entrada máxima
101
+ if entrada_max > 0:
102
+ cotas = [c for c in cotas if calcular_entrada_total(c) <= entrada_max]
103
+
104
+ # Ordena por crédito
105
+ cotas.sort(key=lambda x: float(x.get('valor_credito', 0)))
106
+
107
+ # Gera combinações
108
+ resultados = []
109
+
110
+ for cota in cotas[:30]: # Limita para performance
111
+ entrada_total = calcular_entrada_total(cota)
112
+
113
+ if parcela_max > 0 and float(cota.get('valor_parcela', 0)) > parcela_max:
114
+ continue
115
+
116
+ resultado = {
117
+ 'administradora': get_administradora_nome(cota.get('administradora_id')),
118
+ 'codigo': cota.get('codigo'),
119
+ 'credito': float(cota.get('valor_credito', 0)),
120
+ 'entrada_total': entrada_total,
121
+ 'parcela': float(cota.get('valor_parcela', 0)),
122
+ 'prazo': cota.get('parcelas', 0),
123
+ 'saldo_devedor': float(cota.get('saldo', 0)),
124
+ 'categoria': categoria
125
+ }
126
+
127
+ # Adiciona campos extras
128
+ credito_real = resultado['credito'] - entrada_total
129
+ taxa_total = resultado['saldo_devedor'] - credito_real
130
+ taxa_percentual = (taxa_total / credito_real * 100) if credito_real > 0 else 0
131
+
132
+ resultado['credito_liquido'] = credito_real
133
+ resultado['taxa_total'] = taxa_total
134
+ resultado['taxa_percentual'] = taxa_percentual
135
+ resultado['valor_final'] = entrada_total + resultado['saldo_devedor']
136
+
137
+ # Taxas mensal e anual
138
+ if resultado['prazo'] > 0:
139
+ resultado['taxa_mensal'] = taxa_percentual / resultado['prazo']
140
+ resultado['taxa_anual'] = resultado['taxa_mensal'] * 12
141
+ else:
142
+ resultado['taxa_mensal'] = 0
143
+ resultado['taxa_anual'] = 0
144
+
145
+ # Tempo de parcelas (escalonado)
146
+ if resultado['prazo'] > 21:
147
+ resultado['parcelas_1a21'] = resultado['parcela']
148
+ resultado['parcelas_22a27'] = resultado['parcela'] * 0.44
149
+ else:
150
+ resultado['parcelas_1a21'] = resultado['parcela']
151
+ resultado['parcelas_22a27'] = 0
152
+
153
+ resultados.append(resultado)
154
+
155
+ return jsonify({
156
+ 'status': 'success',
157
+ 'combinacoes': resultados[:15],
158
+ 'total_encontrado': len(resultados)
159
+ })
160
+
161
  except Exception as e:
162
+ logger.error(f"Erro em buscar_combinacoes: {str(e)}")
163
+ return jsonify({'error': str(e)}), 500
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
164
 
165
+ @app.route('/api/detalhes_cota/<int:cota_id>', methods=['GET'])
166
+ def detalhes_cota(cota_id):
167
+ """Proxy para o endpoint de detalhes da API real"""
168
  try:
169
+ response = requests.get(f"{API_BASE_URL}/api/detalhes_cota/{cota_id}")
170
+ return jsonify(response.json()), response.status_code
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
171
  except Exception as e:
172
+ return jsonify({'error': str(e)}), 500
 
173
 
174
+ @app.route('/api/somar_cotas', methods=['POST'])
175
+ def somar_cotas():
176
+ """Proxy para o endpoint de soma da API real"""
177
+ try:
178
+ response = requests.post(
179
+ f"{API_BASE_URL}/api/somar_cotas",
180
+ json=request.get_json(),
181
+ headers={'Content-Type': 'application/json'}
182
+ )
183
+ return jsonify(response.json()), response.status_code
184
+ except Exception as e:
185
+ return jsonify({'error': str(e)}), 500
186
+
187
+ @app.route('/health', methods=['GET'])
188
+ def health_check():
189
+ """Endpoint de saúde"""
190
+ return jsonify({
191
+ 'status': 'healthy',
192
+ 'timestamp': datetime.now().isoformat(),
193
+ 'api_connected': True
194
+ }), 200
195
+
196
+ def get_admin_map():
197
+ """Retorna mapeamento de administradoras"""
198
+ return {
199
+ 1: 'Itaú', 2: 'Bradesco', 3: 'Santander', 4: 'Honda', 5: 'Volkswagen',
200
+ 6: 'Serello', 7: 'Ademicon', 8: 'Remaza', 9: 'Porto Seguro VP',
201
+ 10: 'Banrisul', 11: 'Embracon', 12: 'Serrana', 13: 'Caixa XS5',
202
+ 14: 'Caixa CNP', 15: 'Unifisa', 16: 'HS Consorcios', 17: 'Caixa Seguradora',
203
+ 18: 'Porto Seguro', 19: 'Mycon', 20: 'Rodobens', 21: 'Ford',
204
+ 22: 'Sicoob Unicoob', 23: 'Zema', 24: 'Maggi', 25: 'Ancora', 26: 'Yamaha',
205
+ 27: 'Sponchiado', 28: 'Scania', 29: 'Disal', 30: 'Servopa', 31: 'Canopus',
206
+ 32: 'Bradesco', 33: 'Banco do Brasil', 34: 'Santander', 35: 'Sicredi'
207
+ }
208
+
209
+ if __name__ == '__main__':
210
+ app.run(host='0.0.0.0', port=5003, debug=True)