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__":
|