Spaces:
Build error
Build error
Update src/streamlit_app.py
Browse files- src/streamlit_app.py +92 -98
src/streamlit_app.py
CHANGED
|
@@ -16,34 +16,74 @@ st.set_page_config(
|
|
| 16 |
st.markdown("""
|
| 17 |
<style>
|
| 18 |
.stMetric {
|
| 19 |
-
border-radius: 10px;
|
| 20 |
-
|
| 21 |
-
background-color: #FFFFFF;
|
| 22 |
-
border: 1px solid #E6E6E6;
|
| 23 |
-
box-shadow: 0 4px 8px rgba(0,0,0,0.04);
|
| 24 |
}
|
| 25 |
.stTabs [data-baseweb="tab-list"] { gap: 24px; }
|
| 26 |
.stTabs [data-baseweb="tab"] {
|
| 27 |
-
height: 50px;
|
| 28 |
-
background-color: #F0F2F6;
|
| 29 |
-
border-radius: 4px 4px 0px 0px;
|
| 30 |
-
padding: 10px 15px;
|
| 31 |
}
|
| 32 |
.stTabs [aria-selected="true"] { background-color: #FFFFFF; }
|
| 33 |
</style>
|
| 34 |
""", unsafe_allow_html=True)
|
| 35 |
|
| 36 |
|
| 37 |
-
# --- 3.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
@st.cache_data
|
| 39 |
-
def
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 43 |
meses = ['Jan', 'Fev', 'Mar', 'Abr', 'Mai', 'Jun', 'Jul', 'Ago', 'Set', 'Out', 'Nov', 'Dez']
|
| 44 |
dados_2024 = {
|
| 45 |
-
'Mes': meses,
|
| 46 |
-
'Papel_Papelao': [8047, 11287, 8184, 10183, 5699, 5830, 7465, 5600, 2960, 5175, 9656, 3960],
|
| 47 |
'Plastico': [6353, 8771, 6993, 8050, 4880, 5296, 5937, 4747, 2446, 4109, 7667, 3367],
|
| 48 |
'Metal': [1061, 2025, 1121, 1832, 716, 936, 1553, 904, 361, 630, 1904, 569],
|
| 49 |
'Vidro': [5248, 6929, 6014, 5821, 3697, 3655, 4950, 3360, 1580, 3261, 6173, 2357]
|
|
@@ -52,8 +92,7 @@ def carregar_dados_e_funcoes():
|
|
| 52 |
df_2024_numeric = df_2024.drop(columns='Mes')
|
| 53 |
|
| 54 |
dados_anuais = {
|
| 55 |
-
'Ano': [2022, 2023, 2024],
|
| 56 |
-
'Papel_Papelao': [18780, 58718, df_2024_numeric['Papel_Papelao'].sum()],
|
| 57 |
'Plastico': [5340, 1041, df_2024_numeric['Plastico'].sum()],
|
| 58 |
'Metal': [1300, 1737, df_2024_numeric['Metal'].sum()],
|
| 59 |
'Vidro': [0, 725, df_2024_numeric['Vidro'].sum()]
|
|
@@ -61,69 +100,22 @@ def carregar_dados_e_funcoes():
|
|
| 61 |
df_anuais = pd.DataFrame(dados_anuais).set_index('Ano')
|
| 62 |
|
| 63 |
precos_iniciais = {'Papel_Papelao': 0.50, 'Plastico': 0.80, 'Metal': 2.00, 'Vidro': 0.30}
|
|
|
|
| 64 |
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
def formatar_percentual(valor): return f"{valor:.2%}" if pd.notna(valor) and not np.isinf(valor) else "N/A"
|
| 68 |
-
|
| 69 |
-
def calcular_vpl(fluxo_caixa, taxa_desconto):
|
| 70 |
-
try: return npf.npv(taxa_desconto, fluxo_caixa)
|
| 71 |
-
except: return np.nan
|
| 72 |
-
|
| 73 |
-
def calcular_tir(fluxo_caixa):
|
| 74 |
-
try:
|
| 75 |
-
return npf.irr(fluxo_caixa) if np.any(np.sign(fluxo_caixa)) and np.any(np.sign(fluxo_caixa) * -1) else np.nan
|
| 76 |
-
except: return np.nan
|
| 77 |
-
|
| 78 |
-
def calcular_mtir(fluxo_caixa, taxa_desconto, taxa_reinvestimento):
|
| 79 |
-
try:
|
| 80 |
-
return npf.mirr(fluxo_caixa, taxa_desconto, taxa_reinvestimento) if np.any(np.sign(fluxo_caixa)) and np.any(np.sign(fluxo_caixa) * -1) else np.nan
|
| 81 |
-
except: return np.nan
|
| 82 |
-
|
| 83 |
-
def calcular_payback_descontado(fluxo_caixa, taxa_desconto):
|
| 84 |
-
investimento_inicial = abs(fluxo_caixa[0])
|
| 85 |
-
fluxo_acumulado_descontado = 0
|
| 86 |
-
for periodo, valor in enumerate(fluxo_caixa[1:], 1):
|
| 87 |
-
fluxo_acumulado_descontado += valor / ((1 + taxa_desconto) ** periodo)
|
| 88 |
-
if fluxo_acumulado_descontado >= investimento_inicial:
|
| 89 |
-
ultimo_fluxo_necessario = investimento_inicial - (fluxo_acumulado_descontado - (valor / ((1 + taxa_desconto) ** periodo)))
|
| 90 |
-
return (periodo - 1) + (ultimo_fluxo_necessario / (valor / ((1 + taxa_desconto) ** periodo)))
|
| 91 |
-
return np.inf
|
| 92 |
-
|
| 93 |
-
def simular_faturamento_bootstrap(_df_dados_mensais_numeric, precos_dict, cenarios_dict, n_simulacoes=2000, seed=42):
|
| 94 |
-
np.random.seed(seed)
|
| 95 |
-
faturamentos_simulados = {nome: [] for nome in cenarios_dict.keys()}
|
| 96 |
-
for nome_cen, fator in cenarios_dict.items():
|
| 97 |
-
for _ in range(n_simulacoes):
|
| 98 |
-
df_amostrado = _df_dados_mensais_numeric.sample(n=12, replace=True)
|
| 99 |
-
faturamento_anual_iteracao = sum(df_amostrado[material].sum() * preco for material, preco in precos_dict.items())
|
| 100 |
-
faturamentos_simulados[nome_cen].append(faturamento_anual_iteracao * fator)
|
| 101 |
-
return {nome: np.array(data) for nome, data in faturamentos_simulados.items()}
|
| 102 |
-
|
| 103 |
-
def gerar_fluxo_caixa_projeto(investimento_inicial, receita_anual_base, custos_operacionais_anuais, horizonte_anos, taxa_crescimento):
|
| 104 |
-
fluxo_caixa = [-abs(investimento_inicial)]
|
| 105 |
-
for ano in range(1, horizonte_anos + 1):
|
| 106 |
-
fator_crescimento = (1 + taxa_crescimento) ** (ano - 1)
|
| 107 |
-
fluxo_liquido_ano = (receita_anual_base * fator_crescimento) - (custos_operacionais_anuais * fator_crescimento)
|
| 108 |
-
fluxo_caixa.append(fluxo_liquido_ano)
|
| 109 |
-
return fluxo_caixa
|
| 110 |
-
|
| 111 |
-
return df_2024, df_anuais, precos_iniciais, df_2024_numeric, locals()
|
| 112 |
-
|
| 113 |
-
# --- 4. EXECUÇÃO INICIAL E SIDEBAR DE CONTROLES ---
|
| 114 |
-
df_2024, df_anuais, precos_iniciais, df_2024_numeric, funcoes = carregar_dados_e_funcoes()
|
| 115 |
|
| 116 |
st.sidebar.title(" Painel de Controle")
|
| 117 |
st.sidebar.markdown("Use os menus abaixo para navegar entre as análises e ajustar os parâmetros do projeto.")
|
| 118 |
st.sidebar.divider()
|
| 119 |
|
| 120 |
-
pagina_selecionada = st.sidebar.radio(
|
| 121 |
-
"Menu de Navegação",
|
| 122 |
["🔎 Análise Exploratória (EDA)", "🎯 Simulação de Faturamento", "📊 Análise de Viabilidade"],
|
| 123 |
captions=["Análise dos dados históricos", "Projeção de receitas via simulação", "Cálculos de VPL, TIR e Risco"]
|
| 124 |
)
|
| 125 |
st.sidebar.divider()
|
| 126 |
|
|
|
|
| 127 |
with st.sidebar.expander("⚙️ Premissas Financeiras do Projeto", expanded=True):
|
| 128 |
investimento_inicial = st.number_input("Investimento Inicial (R$)", min_value=1000.0, value=150000.0, step=10000.0)
|
| 129 |
custos_operacionais = st.number_input("Custos Operacionais Anuais (R$)", min_value=0.0, value=50000.0, step=5000.0)
|
|
@@ -140,20 +132,21 @@ with st.sidebar.expander("📈 Preços e Cenários de Simulação", expanded=Fal
|
|
| 140 |
st.sidebar.divider()
|
| 141 |
st.sidebar.info(f"Última atualização: {pd.Timestamp.now(tz='America/Sao_Paulo').strftime('%d/%m/%Y %H:%M')}")
|
| 142 |
|
|
|
|
|
|
|
|
|
|
| 143 |
|
| 144 |
-
# --- 5. LÓGICA DE RENDERIZAÇÃO DAS PÁGINAS ---
|
| 145 |
if pagina_selecionada == "🔎 Análise Exploratória (EDA)":
|
|
|
|
| 146 |
st.title("🔎 Análise Exploratória dos Dados de Coleta")
|
| 147 |
st.markdown("O primeiro passo para qualquer projeção é entender o passado. Aqui, exploramos os dados históricos de coleta para identificar padrões, sazonalidades e anomalias.")
|
| 148 |
st.divider()
|
| 149 |
-
|
| 150 |
st.subheader("Evolução da Coleta Total Anual (Histórico)")
|
| 151 |
df_coleta_anual = df_anuais.sum(axis=1).reset_index()
|
| 152 |
df_coleta_anual.columns = ['Ano', 'Coleta Total (kg)']
|
| 153 |
fig_evol_coleta = px.bar(df_coleta_anual, x='Ano', y='Coleta Total (kg)', text_auto='.3s', title="Volume Total Anual de Materiais Coletados")
|
| 154 |
st.plotly_chart(fig_evol_coleta, use_container_width=True)
|
| 155 |
st.warning("**Observação:** Note a queda acentuada na coleta de Plástico e Metal em 2023 em comparação com 2022 nos dados fornecidos. É crucial investigar se isso reflete a realidade operacional ou uma anomalia nos dados antes de prosseguir com projeções críticas.")
|
| 156 |
-
|
| 157 |
col1, col2 = st.columns(2)
|
| 158 |
with col1:
|
| 159 |
st.subheader("Coleta Mensal por Material (2024)")
|
|
@@ -166,13 +159,14 @@ if pagina_selecionada == "🔎 Análise Exploratória (EDA)":
|
|
| 166 |
st.plotly_chart(fig_boxplot, use_container_width=True)
|
| 167 |
st.info("O **gráfico de linhas** mostra a tendência e sazonalidade ao longo do ano. O **boxplot** revela a volatilidade de cada material: caixas mais 'altas' indicam maior variação mensal, o que se traduz em maior risco e incerteza no faturamento.")
|
| 168 |
|
|
|
|
| 169 |
elif pagina_selecionada == "🎯 Simulação de Faturamento":
|
| 170 |
st.title("🎯 Simulação e Projeção de Faturamento Anual")
|
| 171 |
st.markdown("Utilizando a técnica de bootstrapping, simulamos 2.000 possíveis anos de faturamento com base na volatilidade dos dados de 2024. Isso nos dá uma visão probabilística das receitas do projeto.")
|
| 172 |
st.divider()
|
| 173 |
|
| 174 |
cenarios = {'Pessimista': fator_pessimista, 'Base': 1.0, 'Otimista': fator_otimista}
|
| 175 |
-
simulacoes_faturamento =
|
| 176 |
|
| 177 |
kpi1, kpi2, kpi3 = st.columns(3)
|
| 178 |
media_faturamento_base = np.mean(simulacoes_faturamento['Base'])
|
|
@@ -180,8 +174,8 @@ elif pagina_selecionada == "🎯 Simulação de Faturamento":
|
|
| 180 |
faturamento_material_base = {m: df_2024_numeric[m].sum() * p for m, p in precos_editaveis.items()}
|
| 181 |
material_mais_rentavel = max(faturamento_material_base, key=faturamento_material_base.get)
|
| 182 |
|
| 183 |
-
kpi1.metric("Faturamento Anual Médio (Base)",
|
| 184 |
-
kpi2.metric("Intervalo de Confiança 90% (Base)", f"{
|
| 185 |
kpi3.metric("Material Mais Rentável (Base 2024)", material_mais_rentavel.replace('_', ' '))
|
| 186 |
st.divider()
|
| 187 |
|
|
@@ -204,27 +198,27 @@ elif pagina_selecionada == "📊 Análise de Viabilidade":
|
|
| 204 |
st.markdown("Esta é a etapa final, onde combinamos as projeções de receita com as premissas de investimento e custos para avaliar a viabilidade do projeto sob a ótica do risco.")
|
| 205 |
|
| 206 |
cenarios = {'Pessimista': fator_pessimista, 'Base': 1.0, 'Otimista': fator_otimista}
|
| 207 |
-
simulacoes_faturamento =
|
| 208 |
|
| 209 |
cenario_analise = st.radio("Selecione o cenário para a análise detalhada:", options=list(cenarios.keys()), index=1, horizontal=True)
|
| 210 |
st.divider()
|
| 211 |
|
| 212 |
receita_anual_media = np.mean(simulacoes_faturamento[cenario_analise])
|
| 213 |
-
fluxo_caixa_projeto =
|
| 214 |
|
| 215 |
-
vpl =
|
| 216 |
-
tir =
|
| 217 |
-
mtir =
|
| 218 |
-
payback_desc =
|
| 219 |
|
| 220 |
tab_kpis, tab_fluxo, tab_risco, tab_resumo = st.tabs(["📈 Indicadores Chave", "📂 Fluxo de Caixa", "🎲 Análise de Risco (Monte Carlo)", "📋 Resumo Executivo"])
|
| 221 |
|
| 222 |
with tab_kpis:
|
| 223 |
st.subheader(f"Indicadores para o Cenário: {cenario_analise}")
|
| 224 |
col1, col2, col3, col4 = st.columns(4)
|
| 225 |
-
col1.metric("VPL",
|
| 226 |
-
col2.metric("TIR",
|
| 227 |
-
col3.metric("MTIR",
|
| 228 |
col4.metric("Payback Descontado", f"{payback_desc:.1f} anos" if not np.isinf(payback_desc) else "Não recupera")
|
| 229 |
|
| 230 |
with tab_fluxo:
|
|
@@ -241,15 +235,15 @@ elif pagina_selecionada == "📊 Análise de Viabilidade":
|
|
| 241 |
with tab_risco:
|
| 242 |
st.subheader(f"Simulação Monte Carlo para o VPL (Cenário: {cenario_analise})")
|
| 243 |
receitas_mc = np.random.choice(simulacoes_faturamento[cenario_analise], size=5000, replace=True)
|
| 244 |
-
vpls_mc = [
|
| 245 |
|
| 246 |
prob_viabilidade = (np.array(vpls_mc) >= 0).mean()
|
| 247 |
vpl_medio_mc = np.mean(vpls_mc)
|
| 248 |
|
| 249 |
mc1, mc2, mc3 = st.columns(3)
|
| 250 |
-
mc1.metric("VPL Médio Simulado",
|
| 251 |
-
mc2.metric("Probabilidade de Viabilidade",
|
| 252 |
-
mc3.metric("Intervalo 90% do VPL", f"{
|
| 253 |
|
| 254 |
fig_mc = px.histogram(x=vpls_mc, nbins=50, title=f"Distribuição do VPL ({len(vpls_mc)} Simulações)")
|
| 255 |
fig_mc.add_vline(x=0, line_dash="dash", line_color="red", annotation_text="Viabilidade")
|
|
@@ -258,15 +252,15 @@ elif pagina_selecionada == "📊 Análise de Viabilidade":
|
|
| 258 |
with tab_resumo:
|
| 259 |
st.subheader("Resumo Executivo e Parecer Final")
|
| 260 |
conclusoes = []
|
| 261 |
-
if vpl >= 0: conclusoes.append(f"✅ **Projeto VIÁVEL** no cenário '{cenario_analise}', com um VPL de **{
|
| 262 |
-
else: conclusoes.append(f"❌ **Projeto INVIÁVEL** no cenário '{cenario_analise}', com um VPL de **{
|
| 263 |
|
| 264 |
-
if not pd.isna(tir) and tir > taxa_desconto: conclusoes.append(f"✅ A **TIR de {
|
| 265 |
-
else: conclusoes.append(f"❌ A **TIR de {
|
| 266 |
|
| 267 |
-
if prob_viabilidade > 0.75: conclusoes.append(f"✅ A simulação de Monte Carlo aponta uma **alta probabilidade de sucesso de {
|
| 268 |
-
elif prob_viabilidade > 0.5: conclusoes.append(f"⚠️ A probabilidade de sucesso de **{
|
| 269 |
-
else: conclusoes.append(f"❌ A probabilidade de sucesso de **{
|
| 270 |
|
| 271 |
st.markdown("#### Conclusões Chave:")
|
| 272 |
for c in conclusoes: st.markdown(f"- {c}")
|
|
@@ -275,6 +269,6 @@ elif pagina_selecionada == "📊 Análise de Viabilidade":
|
|
| 275 |
if vpl > 0 and prob_viabilidade > 0.7:
|
| 276 |
st.success("**RECOMENDAÇÃO: APROVAR O PROJETO.** Os indicadores são fortemente positivos e a análise de risco confere robustez à decisão.")
|
| 277 |
elif vpl > 0 and prob_viabilidade > 0.5:
|
| 278 |
-
st.warning("**RECOMENDAÇÃO: APROVAR COM CAUTELA.** O projeto é viável
|
| 279 |
else:
|
| 280 |
-
st.error("**RECOMENDAÇÃO: REAVALIAR OU REJEITAR O PROJETO.** Os indicadores atuais, combinados com o perfil de risco, não justificam o investimento. Revisar premissas de receita, custos ou investimento.")
|
|
|
|
| 16 |
st.markdown("""
|
| 17 |
<style>
|
| 18 |
.stMetric {
|
| 19 |
+
border-radius: 10px; padding: 15px; background-color: #FFFFFF;
|
| 20 |
+
border: 1px solid #E6E6E6; box-shadow: 0 4px 8px rgba(0,0,0,0.04);
|
|
|
|
|
|
|
|
|
|
| 21 |
}
|
| 22 |
.stTabs [data-baseweb="tab-list"] { gap: 24px; }
|
| 23 |
.stTabs [data-baseweb="tab"] {
|
| 24 |
+
height: 50px; background-color: #F0F2F6; border-radius: 4px 4px 0px 0px; padding: 10px 15px;
|
|
|
|
|
|
|
|
|
|
| 25 |
}
|
| 26 |
.stTabs [aria-selected="true"] { background-color: #FFFFFF; }
|
| 27 |
</style>
|
| 28 |
""", unsafe_allow_html=True)
|
| 29 |
|
| 30 |
|
| 31 |
+
# --- 3. DEFINIÇÃO DAS FUNÇÕES GLOBAIS ---
|
| 32 |
+
# Todas as funções foram movidas para o escopo global para evitar o erro de cache.
|
| 33 |
+
|
| 34 |
+
def formatar_brl(valor): return f"R$ {valor:,.2f}"
|
| 35 |
+
def formatar_percentual(valor): return f"{valor:.2%}" if pd.notna(valor) and not np.isinf(valor) else "N/A"
|
| 36 |
+
|
| 37 |
+
def calcular_vpl(fluxo_caixa, taxa_desconto):
|
| 38 |
+
try: return npf.npv(taxa_desconto, fluxo_caixa)
|
| 39 |
+
except: return np.nan
|
| 40 |
+
|
| 41 |
+
def calcular_tir(fluxo_caixa):
|
| 42 |
+
try:
|
| 43 |
+
return npf.irr(fluxo_caixa) if np.any(np.sign(fluxo_caixa)) and np.any(np.sign(fluxo_caixa) * -1) else np.nan
|
| 44 |
+
except: return np.nan
|
| 45 |
+
|
| 46 |
+
def calcular_mtir(fluxo_caixa, taxa_desconto, taxa_reinvestimento):
|
| 47 |
+
try:
|
| 48 |
+
return npf.mirr(fluxo_caixa, taxa_desconto, taxa_reinvestimento) if np.any(np.sign(fluxo_caixa)) and np.any(np.sign(fluxo_caixa) * -1) else np.nan
|
| 49 |
+
except: return np.nan
|
| 50 |
+
|
| 51 |
+
def calcular_payback_descontado(fluxo_caixa, taxa_desconto):
|
| 52 |
+
investimento_inicial = abs(fluxo_caixa[0])
|
| 53 |
+
fluxo_acumulado_descontado = 0
|
| 54 |
+
for periodo, valor in enumerate(fluxo_caixa[1:], 1):
|
| 55 |
+
fluxo_acumulado_descontado += valor / ((1 + taxa_desconto) ** periodo)
|
| 56 |
+
if fluxo_acumulado_descontado >= investimento_inicial:
|
| 57 |
+
ultimo_fluxo_necessario = investimento_inicial - (fluxo_acumulado_descontado - (valor / ((1 + taxa_desconto) ** periodo)))
|
| 58 |
+
return (periodo - 1) + (ultimo_fluxo_necessario / (valor / ((1 + taxa_desconto) ** periodo)))
|
| 59 |
+
return np.inf
|
| 60 |
+
|
| 61 |
@st.cache_data
|
| 62 |
+
def simular_faturamento_bootstrap(_df_dados_mensais_numeric, precos_dict, cenarios_dict, n_simulacoes=2000, seed=42):
|
| 63 |
+
np.random.seed(seed)
|
| 64 |
+
faturamentos_simulados = {nome: [] for nome in cenarios_dict.keys()}
|
| 65 |
+
for nome_cen, fator in cenarios_dict.items():
|
| 66 |
+
for _ in range(n_simulacoes):
|
| 67 |
+
df_amostrado = _df_dados_mensais_numeric.sample(n=12, replace=True)
|
| 68 |
+
faturamento_anual_iteracao = sum(df_amostrado[material].sum() * preco for material, preco in precos_dict.items())
|
| 69 |
+
faturamentos_simulados[nome_cen].append(faturamento_anual_iteracao * fator)
|
| 70 |
+
return {nome: np.array(data) for nome, data in faturamentos_simulados.items()}
|
| 71 |
+
|
| 72 |
+
def gerar_fluxo_caixa_projeto(investimento_inicial, receita_anual_base, custos_operacionais_anuais, horizonte_anos, taxa_crescimento):
|
| 73 |
+
fluxo_caixa = [-abs(investimento_inicial)]
|
| 74 |
+
for ano in range(1, horizonte_anos + 1):
|
| 75 |
+
fator_crescimento = (1 + taxa_crescimento) ** (ano - 1)
|
| 76 |
+
fluxo_liquido_ano = (receita_anual_base * fator_crescimento) - (custos_operacionais_anuais * fator_crescimento)
|
| 77 |
+
fluxo_caixa.append(fluxo_liquido_ano)
|
| 78 |
+
return fluxo_caixa
|
| 79 |
+
|
| 80 |
+
# --- 4. CARREGAMENTO DE DADOS (FUNÇÃO CACHEADA) ---
|
| 81 |
+
@st.cache_data
|
| 82 |
+
def carregar_dados():
|
| 83 |
+
"""Carrega apenas os dados iniciais do projeto. É seguro cachear esta função."""
|
| 84 |
meses = ['Jan', 'Fev', 'Mar', 'Abr', 'Mai', 'Jun', 'Jul', 'Ago', 'Set', 'Out', 'Nov', 'Dez']
|
| 85 |
dados_2024 = {
|
| 86 |
+
'Mes': meses, 'Papel_Papelao': [8047, 11287, 8184, 10183, 5699, 5830, 7465, 5600, 2960, 5175, 9656, 3960],
|
|
|
|
| 87 |
'Plastico': [6353, 8771, 6993, 8050, 4880, 5296, 5937, 4747, 2446, 4109, 7667, 3367],
|
| 88 |
'Metal': [1061, 2025, 1121, 1832, 716, 936, 1553, 904, 361, 630, 1904, 569],
|
| 89 |
'Vidro': [5248, 6929, 6014, 5821, 3697, 3655, 4950, 3360, 1580, 3261, 6173, 2357]
|
|
|
|
| 92 |
df_2024_numeric = df_2024.drop(columns='Mes')
|
| 93 |
|
| 94 |
dados_anuais = {
|
| 95 |
+
'Ano': [2022, 2023, 2024], 'Papel_Papelao': [18780, 58718, df_2024_numeric['Papel_Papelao'].sum()],
|
|
|
|
| 96 |
'Plastico': [5340, 1041, df_2024_numeric['Plastico'].sum()],
|
| 97 |
'Metal': [1300, 1737, df_2024_numeric['Metal'].sum()],
|
| 98 |
'Vidro': [0, 725, df_2024_numeric['Vidro'].sum()]
|
|
|
|
| 100 |
df_anuais = pd.DataFrame(dados_anuais).set_index('Ano')
|
| 101 |
|
| 102 |
precos_iniciais = {'Papel_Papelao': 0.50, 'Plastico': 0.80, 'Metal': 2.00, 'Vidro': 0.30}
|
| 103 |
+
return df_2024, df_anuais, precos_iniciais, df_2024_numeric
|
| 104 |
|
| 105 |
+
# --- 5. EXECUÇÃO INICIAL E SIDEBAR DE CONTROLES ---
|
| 106 |
+
df_2024, df_anuais, precos_iniciais, df_2024_numeric = carregar_dados()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 107 |
|
| 108 |
st.sidebar.title(" Painel de Controle")
|
| 109 |
st.sidebar.markdown("Use os menus abaixo para navegar entre as análises e ajustar os parâmetros do projeto.")
|
| 110 |
st.sidebar.divider()
|
| 111 |
|
| 112 |
+
pagina_selecionada = st.sidebar.radio( "Menu de Navegação",
|
|
|
|
| 113 |
["🔎 Análise Exploratória (EDA)", "🎯 Simulação de Faturamento", "📊 Análise de Viabilidade"],
|
| 114 |
captions=["Análise dos dados históricos", "Projeção de receitas via simulação", "Cálculos de VPL, TIR e Risco"]
|
| 115 |
)
|
| 116 |
st.sidebar.divider()
|
| 117 |
|
| 118 |
+
# Parâmetros Editáveis na Sidebar (código igual ao anterior)
|
| 119 |
with st.sidebar.expander("⚙️ Premissas Financeiras do Projeto", expanded=True):
|
| 120 |
investimento_inicial = st.number_input("Investimento Inicial (R$)", min_value=1000.0, value=150000.0, step=10000.0)
|
| 121 |
custos_operacionais = st.number_input("Custos Operacionais Anuais (R$)", min_value=0.0, value=50000.0, step=5000.0)
|
|
|
|
| 132 |
st.sidebar.divider()
|
| 133 |
st.sidebar.info(f"Última atualização: {pd.Timestamp.now(tz='America/Sao_Paulo').strftime('%d/%m/%Y %H:%M')}")
|
| 134 |
|
| 135 |
+
# --- 6. LÓGICA DE RENDERIZAÇÃO DAS PÁGINAS ---
|
| 136 |
+
# O código aqui dentro permanece o mesmo, mas agora chama as funções globais diretamente.
|
| 137 |
+
# Exemplo de chamada direta: formatar_brl(vpl) em vez de funcoes['formatar_brl'](vpl)
|
| 138 |
|
|
|
|
| 139 |
if pagina_selecionada == "🔎 Análise Exploratória (EDA)":
|
| 140 |
+
# ... (código desta página não precisa de alteração nas chamadas)
|
| 141 |
st.title("🔎 Análise Exploratória dos Dados de Coleta")
|
| 142 |
st.markdown("O primeiro passo para qualquer projeção é entender o passado. Aqui, exploramos os dados históricos de coleta para identificar padrões, sazonalidades e anomalias.")
|
| 143 |
st.divider()
|
|
|
|
| 144 |
st.subheader("Evolução da Coleta Total Anual (Histórico)")
|
| 145 |
df_coleta_anual = df_anuais.sum(axis=1).reset_index()
|
| 146 |
df_coleta_anual.columns = ['Ano', 'Coleta Total (kg)']
|
| 147 |
fig_evol_coleta = px.bar(df_coleta_anual, x='Ano', y='Coleta Total (kg)', text_auto='.3s', title="Volume Total Anual de Materiais Coletados")
|
| 148 |
st.plotly_chart(fig_evol_coleta, use_container_width=True)
|
| 149 |
st.warning("**Observação:** Note a queda acentuada na coleta de Plástico e Metal em 2023 em comparação com 2022 nos dados fornecidos. É crucial investigar se isso reflete a realidade operacional ou uma anomalia nos dados antes de prosseguir com projeções críticas.")
|
|
|
|
| 150 |
col1, col2 = st.columns(2)
|
| 151 |
with col1:
|
| 152 |
st.subheader("Coleta Mensal por Material (2024)")
|
|
|
|
| 159 |
st.plotly_chart(fig_boxplot, use_container_width=True)
|
| 160 |
st.info("O **gráfico de linhas** mostra a tendência e sazonalidade ao longo do ano. O **boxplot** revela a volatilidade de cada material: caixas mais 'altas' indicam maior variação mensal, o que se traduz em maior risco e incerteza no faturamento.")
|
| 161 |
|
| 162 |
+
|
| 163 |
elif pagina_selecionada == "🎯 Simulação de Faturamento":
|
| 164 |
st.title("🎯 Simulação e Projeção de Faturamento Anual")
|
| 165 |
st.markdown("Utilizando a técnica de bootstrapping, simulamos 2.000 possíveis anos de faturamento com base na volatilidade dos dados de 2024. Isso nos dá uma visão probabilística das receitas do projeto.")
|
| 166 |
st.divider()
|
| 167 |
|
| 168 |
cenarios = {'Pessimista': fator_pessimista, 'Base': 1.0, 'Otimista': fator_otimista}
|
| 169 |
+
simulacoes_faturamento = simular_faturamento_bootstrap(df_2024_numeric, precos_editaveis, cenarios)
|
| 170 |
|
| 171 |
kpi1, kpi2, kpi3 = st.columns(3)
|
| 172 |
media_faturamento_base = np.mean(simulacoes_faturamento['Base'])
|
|
|
|
| 174 |
faturamento_material_base = {m: df_2024_numeric[m].sum() * p for m, p in precos_editaveis.items()}
|
| 175 |
material_mais_rentavel = max(faturamento_material_base, key=faturamento_material_base.get)
|
| 176 |
|
| 177 |
+
kpi1.metric("Faturamento Anual Médio (Base)", formatar_brl(media_faturamento_base))
|
| 178 |
+
kpi2.metric("Intervalo de Confiança 90% (Base)", f"{formatar_brl(p05_base)} - {formatar_brl(p95_base)}")
|
| 179 |
kpi3.metric("Material Mais Rentável (Base 2024)", material_mais_rentavel.replace('_', ' '))
|
| 180 |
st.divider()
|
| 181 |
|
|
|
|
| 198 |
st.markdown("Esta é a etapa final, onde combinamos as projeções de receita com as premissas de investimento e custos para avaliar a viabilidade do projeto sob a ótica do risco.")
|
| 199 |
|
| 200 |
cenarios = {'Pessimista': fator_pessimista, 'Base': 1.0, 'Otimista': fator_otimista}
|
| 201 |
+
simulacoes_faturamento = simular_faturamento_bootstrap(df_2024_numeric, precos_editaveis, cenarios)
|
| 202 |
|
| 203 |
cenario_analise = st.radio("Selecione o cenário para a análise detalhada:", options=list(cenarios.keys()), index=1, horizontal=True)
|
| 204 |
st.divider()
|
| 205 |
|
| 206 |
receita_anual_media = np.mean(simulacoes_faturamento[cenario_analise])
|
| 207 |
+
fluxo_caixa_projeto = gerar_fluxo_caixa_projeto(investimento_inicial, receita_anual_media, custos_operacionais, horizonte_projeto, taxa_crescimento)
|
| 208 |
|
| 209 |
+
vpl = calcular_vpl(fluxo_caixa_projeto, taxa_desconto)
|
| 210 |
+
tir = calcular_tir(fluxo_caixa_projeto)
|
| 211 |
+
mtir = calcular_mtir(fluxo_caixa_projeto, taxa_desconto, taxa_reinvestimento)
|
| 212 |
+
payback_desc = calcular_payback_descontado(fluxo_caixa_projeto, taxa_desconto)
|
| 213 |
|
| 214 |
tab_kpis, tab_fluxo, tab_risco, tab_resumo = st.tabs(["📈 Indicadores Chave", "📂 Fluxo de Caixa", "🎲 Análise de Risco (Monte Carlo)", "📋 Resumo Executivo"])
|
| 215 |
|
| 216 |
with tab_kpis:
|
| 217 |
st.subheader(f"Indicadores para o Cenário: {cenario_analise}")
|
| 218 |
col1, col2, col3, col4 = st.columns(4)
|
| 219 |
+
col1.metric("VPL", formatar_brl(vpl), "Viável" if vpl >= 0 else "Inviável")
|
| 220 |
+
col2.metric("TIR", formatar_percentual(tir), "Superior à TMA" if pd.notna(tir) and tir > taxa_desconto else "Inferior à TMA")
|
| 221 |
+
col3.metric("MTIR", formatar_percentual(mtir), "Superior à TMA" if pd.notna(mtir) and mtir > taxa_desconto else "Inferior à TMA")
|
| 222 |
col4.metric("Payback Descontado", f"{payback_desc:.1f} anos" if not np.isinf(payback_desc) else "Não recupera")
|
| 223 |
|
| 224 |
with tab_fluxo:
|
|
|
|
| 235 |
with tab_risco:
|
| 236 |
st.subheader(f"Simulação Monte Carlo para o VPL (Cenário: {cenario_analise})")
|
| 237 |
receitas_mc = np.random.choice(simulacoes_faturamento[cenario_analise], size=5000, replace=True)
|
| 238 |
+
vpls_mc = [calcular_vpl(gerar_fluxo_caixa_projeto(investimento_inicial, r, custos_operacionais, horizonte_projeto, taxa_crescimento), taxa_desconto) for r in receitas_mc]
|
| 239 |
|
| 240 |
prob_viabilidade = (np.array(vpls_mc) >= 0).mean()
|
| 241 |
vpl_medio_mc = np.mean(vpls_mc)
|
| 242 |
|
| 243 |
mc1, mc2, mc3 = st.columns(3)
|
| 244 |
+
mc1.metric("VPL Médio Simulado", formatar_brl(vpl_medio_mc))
|
| 245 |
+
mc2.metric("Probabilidade de Viabilidade", formatar_percentual(prob_viabilidade))
|
| 246 |
+
mc3.metric("Intervalo 90% do VPL", f"{formatar_brl(np.percentile(vpls_mc, 5))} a {formatar_brl(np.percentile(vpls_mc, 95))}")
|
| 247 |
|
| 248 |
fig_mc = px.histogram(x=vpls_mc, nbins=50, title=f"Distribuição do VPL ({len(vpls_mc)} Simulações)")
|
| 249 |
fig_mc.add_vline(x=0, line_dash="dash", line_color="red", annotation_text="Viabilidade")
|
|
|
|
| 252 |
with tab_resumo:
|
| 253 |
st.subheader("Resumo Executivo e Parecer Final")
|
| 254 |
conclusoes = []
|
| 255 |
+
if vpl >= 0: conclusoes.append(f"✅ **Projeto VIÁVEL** no cenário '{cenario_analise}', com um VPL de **{formatar_brl(vpl)}**.")
|
| 256 |
+
else: conclusoes.append(f"❌ **Projeto INVIÁVEL** no cenário '{cenario_analise}', com um VPL de **{formatar_brl(vpl)}**.")
|
| 257 |
|
| 258 |
+
if not pd.isna(tir) and tir > taxa_desconto: conclusoes.append(f"✅ A **TIR de {formatar_percentual(tir)} é superior à TMA de {formatar_percentual(taxa_desconto)}**, reforçando a atratividade.")
|
| 259 |
+
else: conclusoes.append(f"❌ A **TIR de {formatar_percentual(tir)} é inferior à TMA**, um forte indicador contra o investimento.")
|
| 260 |
|
| 261 |
+
if prob_viabilidade > 0.75: conclusoes.append(f"✅ A simulação de Monte Carlo aponta uma **alta probabilidade de sucesso de {formatar_percentual(prob_viabilidade)}**.")
|
| 262 |
+
elif prob_viabilidade > 0.5: conclusoes.append(f"⚠️ A probabilidade de sucesso de **{formatar_percentual(prob_viabilidade)} é moderada**.")
|
| 263 |
+
else: conclusoes.append(f"❌ A probabilidade de sucesso de **{formatar_percentual(prob_viabilidade)} é baixa**, indicando risco elevado.")
|
| 264 |
|
| 265 |
st.markdown("#### Conclusões Chave:")
|
| 266 |
for c in conclusoes: st.markdown(f"- {c}")
|
|
|
|
| 269 |
if vpl > 0 and prob_viabilidade > 0.7:
|
| 270 |
st.success("**RECOMENDAÇÃO: APROVAR O PROJETO.** Os indicadores são fortemente positivos e a análise de risco confere robustez à decisão.")
|
| 271 |
elif vpl > 0 and prob_viabilidade > 0.5:
|
| 272 |
+
st.warning("**RECOMENDAÇÃO: APROVAR COM CAUTELA.** O projeto é viável, mas a análise de risco mostra sensibilidade. Recomenda-se um plano de monitoramento de riscos focado em garantir as receitas projetadas.")
|
| 273 |
else:
|
| 274 |
+
st.error("**RECOMENDAÇÃO: REAVALIAR OU REJEITAR O PROJETO.** Os indicadores atuais, combinados com o perfil de risco, não justificam o investimento. Revisar premissas de receita, custos ou investimento inicial.")
|