Update app.py
Browse files
app.py
CHANGED
|
@@ -15,21 +15,15 @@ from babel.numbers import format_currency
|
|
| 15 |
|
| 16 |
app = Flask(__name__)
|
| 17 |
|
| 18 |
-
#
|
| 19 |
logging.basicConfig(level=logging.DEBUG)
|
| 20 |
|
| 21 |
-
#
|
| 22 |
openai.api_key = os.getenv("OPENAI_API_KEY")
|
| 23 |
|
| 24 |
-
# Função para formatar valores monetários em BRL
|
| 25 |
def formatar_brl(valor):
|
| 26 |
-
|
| 27 |
-
return format_currency(valor, "BRL", locale="pt_BR")
|
| 28 |
-
except Exception:
|
| 29 |
-
logging.warning(f"Erro ao formatar valor monetário: {valor}")
|
| 30 |
-
return f"R$ {valor:,.2f}"
|
| 31 |
|
| 32 |
-
# Função para converter para float com log e valor default
|
| 33 |
def safe_float(valor, nome, default=0.0):
|
| 34 |
try:
|
| 35 |
f = float(valor)
|
|
@@ -39,7 +33,6 @@ def safe_float(valor, nome, default=0.0):
|
|
| 39 |
logging.error(f"Erro convertendo {nome} com valor '{valor}': {e}")
|
| 40 |
return default
|
| 41 |
|
| 42 |
-
# Função que calcula projeções para os 5 anos
|
| 43 |
def calcular_projecoes(capital, studio_ret, valorizacao, franquia_ret, acoes_ret, renda_fixa):
|
| 44 |
anos = list(range(1, 6))
|
| 45 |
patrimonio_studio = [capital * ((1 + valorizacao / 100) ** ano) for ano in anos]
|
|
@@ -65,7 +58,6 @@ def calcular_projecoes(capital, studio_ret, valorizacao, franquia_ret, acoes_ret
|
|
| 65 |
}
|
| 66 |
return dados, investimentos_finais
|
| 67 |
|
| 68 |
-
# Função para gerar gráfico base64
|
| 69 |
def gerar_grafico(anos, studio_total, franquia, acoes, renda_fixa_valores):
|
| 70 |
plt.figure(figsize=(8, 5))
|
| 71 |
plt.plot(anos, studio_total, label="Studio (Patrimônio + Renda)", marker="o")
|
|
@@ -87,7 +79,6 @@ def gerar_grafico(anos, studio_total, franquia, acoes, renda_fixa_valores):
|
|
| 87 |
grafico_base64 = base64.b64encode(buf.getvalue()).decode("utf-8")
|
| 88 |
return grafico_base64
|
| 89 |
|
| 90 |
-
# Função que gera o relatório com a API OpenAI
|
| 91 |
def gerar_analise_ia(investimentos_finais, capital, patrimonio_studio_final):
|
| 92 |
prompt = f"""
|
| 93 |
Você é um analista financeiro experiente, e está redigindo um relatório para um cliente com capital inicial de R$ {capital:,.2f}. Após 5 anos, os resultados dos investimentos foram:
|
|
@@ -104,23 +95,18 @@ Escreva um relatório claro, didático e consultivo, com no máximo 10 linhas, c
|
|
| 104 |
5. Sugestão de diversificação balanceada para perfil moderado.
|
| 105 |
Use parágrafos e destaque em negrito os títulos dos tópicos. Não use markdown, apenas HTML.
|
| 106 |
"""
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
return texto
|
| 119 |
-
except Exception as e:
|
| 120 |
-
logging.error(f"Erro ao chamar API OpenAI: {e}")
|
| 121 |
-
return "<p>Erro ao gerar análise com IA.</p>"
|
| 122 |
|
| 123 |
-
# Função para gerar PDF a partir de template HTML
|
| 124 |
def render_pdf(template_src, context_dict):
|
| 125 |
html = render_template(template_src, **context_dict)
|
| 126 |
result = io.BytesIO()
|
|
@@ -131,7 +117,6 @@ def render_pdf(template_src, context_dict):
|
|
| 131 |
logging.error(f"Erro pisa.CreatePDF: {pisa_status.err}")
|
| 132 |
return None
|
| 133 |
|
| 134 |
-
# Rota principal com formulário
|
| 135 |
@app.route("/", methods=["GET", "POST"])
|
| 136 |
def index():
|
| 137 |
if request.method == "POST":
|
|
@@ -176,72 +161,51 @@ def index():
|
|
| 176 |
logging.error("Erro na rota /", exc_info=True)
|
| 177 |
return "Erro interno no servidor. Verifique os logs.", 500
|
| 178 |
|
| 179 |
-
# GET simples
|
| 180 |
return render_template("index.html")
|
| 181 |
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
def gerar_pdf():
|
| 185 |
try:
|
| 186 |
-
|
| 187 |
-
dados = {campo: safe_float(request.form.get(campo), campo) for campo in campos}
|
| 188 |
-
|
| 189 |
-
capital = dados["capital"]
|
| 190 |
-
anos = list(range(1, 6))
|
| 191 |
-
|
| 192 |
-
patrimonio_studio = [capital * ((1 + dados["valorizacao"] / 100) ** ano) for ano in anos]
|
| 193 |
-
renda_acumulada_studio = [capital * (((1 + dados["studio_ret"] / 100) ** (12 * ano)) - 1) for ano in anos]
|
| 194 |
-
studio_total = [p + r for p, r in zip(patrimonio_studio, renda_acumulada_studio)]
|
| 195 |
-
patrimonio_final = studio_total[-1]
|
| 196 |
-
|
| 197 |
-
franquia = [capital + (dados["franquia_ret"] * ano) for ano in anos]
|
| 198 |
-
acoes = [capital * ((1 + dados["acoes_ret"] / 100) ** ano) for ano in anos]
|
| 199 |
-
renda_fixa_valores = [capital * ((1 + dados["renda_fixa"] / 100) ** ano) for ano in anos]
|
| 200 |
-
|
| 201 |
-
df = pd.DataFrame({
|
| 202 |
-
"Ano": anos,
|
| 203 |
-
"Studio (Patrimônio + Renda)": studio_total,
|
| 204 |
-
"Franquia": franquia,
|
| 205 |
-
"Ações": acoes,
|
| 206 |
-
"Renda Fixa": renda_fixa_valores
|
| 207 |
-
})
|
| 208 |
-
|
| 209 |
-
investimentos_finais = {
|
| 210 |
-
"Studio": studio_total[-1],
|
| 211 |
-
"Franquia": franquia[-1],
|
| 212 |
-
"Ações": acoes[-1],
|
| 213 |
-
"Renda Fixa": renda_fixa_valores[-1]
|
| 214 |
-
}
|
| 215 |
|
| 216 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 217 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 218 |
df_formatado = df.copy()
|
| 219 |
for col in df.columns:
|
| 220 |
if col != "Ano":
|
| 221 |
df_formatado[col] = df_formatado[col].apply(formatar_brl)
|
| 222 |
-
tabela_html = df_formatado.to_html(index=False, classes="
|
|
|
|
|
|
|
| 223 |
|
| 224 |
-
analise_final = gerar_analise_ia(investimentos_finais, capital,
|
| 225 |
|
| 226 |
data_formatada = format_date(datetime.now(), "d 'de' MMMM 'de' yyyy", locale="pt_BR")
|
| 227 |
data_hoje = f"São Paulo, {data_formatada}"
|
| 228 |
|
| 229 |
-
capital_formatado = formatar_brl(capital)
|
| 230 |
-
studio_ret_formatado = f"{dados['studio_ret']:.2f} %"
|
| 231 |
-
valorizacao_formatado = f"{dados['valorizacao']:.2f} %"
|
| 232 |
-
franquia_ret_formatado = formatar_brl(dados["franquia_ret"])
|
| 233 |
-
acoes_ret_formatado = f"{dados['acoes_ret']:.2f} %"
|
| 234 |
-
renda_fixa_formatado = f"{dados['renda_fixa']:.2f} %"
|
| 235 |
-
inflacao_formatado = f"{dados['inflacao']:.2f} %"
|
| 236 |
-
|
| 237 |
context = {
|
| 238 |
-
"capital_formatado":
|
| 239 |
-
"studio_ret_formatado":
|
| 240 |
-
"valorizacao_formatado":
|
| 241 |
-
"franquia_ret_formatado":
|
| 242 |
-
"acoes_ret_formatado":
|
| 243 |
-
"renda_fixa_formatado":
|
| 244 |
-
"inflacao_formatado":
|
| 245 |
"grafico": grafico_base64,
|
| 246 |
"tabela": tabela_html,
|
| 247 |
"analise_final": analise_final,
|
|
@@ -254,9 +218,9 @@ def gerar_pdf():
|
|
| 254 |
else:
|
| 255 |
return "Erro ao gerar o PDF", 500
|
| 256 |
|
| 257 |
-
except Exception
|
| 258 |
-
logging.error("Erro na rota /
|
| 259 |
-
return
|
| 260 |
|
| 261 |
|
| 262 |
if __name__ == "__main__":
|
|
|
|
| 15 |
|
| 16 |
app = Flask(__name__)
|
| 17 |
|
| 18 |
+
# Logger
|
| 19 |
logging.basicConfig(level=logging.DEBUG)
|
| 20 |
|
| 21 |
+
# OpenAI key da variável de ambiente
|
| 22 |
openai.api_key = os.getenv("OPENAI_API_KEY")
|
| 23 |
|
|
|
|
| 24 |
def formatar_brl(valor):
|
| 25 |
+
return format_currency(valor, "BRL", locale="pt_BR")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
|
|
|
|
| 27 |
def safe_float(valor, nome, default=0.0):
|
| 28 |
try:
|
| 29 |
f = float(valor)
|
|
|
|
| 33 |
logging.error(f"Erro convertendo {nome} com valor '{valor}': {e}")
|
| 34 |
return default
|
| 35 |
|
|
|
|
| 36 |
def calcular_projecoes(capital, studio_ret, valorizacao, franquia_ret, acoes_ret, renda_fixa):
|
| 37 |
anos = list(range(1, 6))
|
| 38 |
patrimonio_studio = [capital * ((1 + valorizacao / 100) ** ano) for ano in anos]
|
|
|
|
| 58 |
}
|
| 59 |
return dados, investimentos_finais
|
| 60 |
|
|
|
|
| 61 |
def gerar_grafico(anos, studio_total, franquia, acoes, renda_fixa_valores):
|
| 62 |
plt.figure(figsize=(8, 5))
|
| 63 |
plt.plot(anos, studio_total, label="Studio (Patrimônio + Renda)", marker="o")
|
|
|
|
| 79 |
grafico_base64 = base64.b64encode(buf.getvalue()).decode("utf-8")
|
| 80 |
return grafico_base64
|
| 81 |
|
|
|
|
| 82 |
def gerar_analise_ia(investimentos_finais, capital, patrimonio_studio_final):
|
| 83 |
prompt = f"""
|
| 84 |
Você é um analista financeiro experiente, e está redigindo um relatório para um cliente com capital inicial de R$ {capital:,.2f}. Após 5 anos, os resultados dos investimentos foram:
|
|
|
|
| 95 |
5. Sugestão de diversificação balanceada para perfil moderado.
|
| 96 |
Use parágrafos e destaque em negrito os títulos dos tópicos. Não use markdown, apenas HTML.
|
| 97 |
"""
|
| 98 |
+
resposta = openai.ChatCompletion.create(
|
| 99 |
+
model="gpt-4o",
|
| 100 |
+
messages=[
|
| 101 |
+
{"role": "system", "content": "Você é um analista financeiro experiente."},
|
| 102 |
+
{"role": "user", "content": prompt}
|
| 103 |
+
],
|
| 104 |
+
max_tokens=500,
|
| 105 |
+
temperature=0.7
|
| 106 |
+
)
|
| 107 |
+
texto = resposta.choices[0].message.content
|
| 108 |
+
return texto
|
|
|
|
|
|
|
|
|
|
|
|
|
| 109 |
|
|
|
|
| 110 |
def render_pdf(template_src, context_dict):
|
| 111 |
html = render_template(template_src, **context_dict)
|
| 112 |
result = io.BytesIO()
|
|
|
|
| 117 |
logging.error(f"Erro pisa.CreatePDF: {pisa_status.err}")
|
| 118 |
return None
|
| 119 |
|
|
|
|
| 120 |
@app.route("/", methods=["GET", "POST"])
|
| 121 |
def index():
|
| 122 |
if request.method == "POST":
|
|
|
|
| 161 |
logging.error("Erro na rota /", exc_info=True)
|
| 162 |
return "Erro interno no servidor. Verifique os logs.", 500
|
| 163 |
|
|
|
|
| 164 |
return render_template("index.html")
|
| 165 |
|
| 166 |
+
@app.route("/relatorio", methods=["POST"])
|
| 167 |
+
def relatorio():
|
|
|
|
| 168 |
try:
|
| 169 |
+
logging.debug(f"Dados recebidos para PDF: {request.form}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 170 |
|
| 171 |
+
capital = safe_float(request.form.get("capital"), "capital")
|
| 172 |
+
studio_ret = safe_float(request.form.get("studio_ret"), "studio_ret")
|
| 173 |
+
valorizacao = safe_float(request.form.get("valorizacao"), "valorizacao")
|
| 174 |
+
franquia_ret = safe_float(request.form.get("franquia_ret"), "franquia_ret")
|
| 175 |
+
acoes_ret = safe_float(request.form.get("acoes_ret"), "acoes_ret")
|
| 176 |
+
renda_fixa = safe_float(request.form.get("renda_fixa"), "renda_fixa")
|
| 177 |
+
inflacao = safe_float(request.form.get("inflacao"), "inflacao")
|
| 178 |
+
|
| 179 |
+
dados, investimentos_finais = calcular_projecoes(capital, studio_ret, valorizacao, franquia_ret, acoes_ret, renda_fixa)
|
| 180 |
|
| 181 |
+
anos = dados["Ano"]
|
| 182 |
+
studio_total = dados["Studio (Patrimônio + Renda)"]
|
| 183 |
+
franquia = dados["Franquia"]
|
| 184 |
+
acoes = dados["Ações"]
|
| 185 |
+
renda_fixa_valores = dados["Renda Fixa"]
|
| 186 |
+
|
| 187 |
+
df = pd.DataFrame(dados)
|
| 188 |
df_formatado = df.copy()
|
| 189 |
for col in df.columns:
|
| 190 |
if col != "Ano":
|
| 191 |
df_formatado[col] = df_formatado[col].apply(formatar_brl)
|
| 192 |
+
tabela_html = df_formatado.to_html(index=False, classes="table table-striped table-sm text-end", border=0)
|
| 193 |
+
|
| 194 |
+
grafico_base64 = gerar_grafico(anos, studio_total, franquia, acoes, renda_fixa_valores)
|
| 195 |
|
| 196 |
+
analise_final = gerar_analise_ia(investimentos_finais, capital, studio_total[-1])
|
| 197 |
|
| 198 |
data_formatada = format_date(datetime.now(), "d 'de' MMMM 'de' yyyy", locale="pt_BR")
|
| 199 |
data_hoje = f"São Paulo, {data_formatada}"
|
| 200 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 201 |
context = {
|
| 202 |
+
"capital_formatado": formatar_brl(capital),
|
| 203 |
+
"studio_ret_formatado": f"{studio_ret:.2f} %",
|
| 204 |
+
"valorizacao_formatado": f"{valorizacao:.2f} %",
|
| 205 |
+
"franquia_ret_formatado": formatar_brl(franquia_ret),
|
| 206 |
+
"acoes_ret_formatado": f"{acoes_ret:.2f} %",
|
| 207 |
+
"renda_fixa_formatado": f"{renda_fixa:.2f} %",
|
| 208 |
+
"inflacao_formatado": f"{inflacao:.2f} %",
|
| 209 |
"grafico": grafico_base64,
|
| 210 |
"tabela": tabela_html,
|
| 211 |
"analise_final": analise_final,
|
|
|
|
| 218 |
else:
|
| 219 |
return "Erro ao gerar o PDF", 500
|
| 220 |
|
| 221 |
+
except Exception:
|
| 222 |
+
logging.error("Erro na rota /relatorio", exc_info=True)
|
| 223 |
+
return "Erro interno ao gerar o PDF. Verifique os logs.", 500
|
| 224 |
|
| 225 |
|
| 226 |
if __name__ == "__main__":
|