overbooking-binomial-siep / src /streamlit_app.py
bfiguei's picture
Update src/streamlit_app.py
69485c9 verified
#!/usr/bin/env python
# coding: utf-8
import streamlit as st
import numpy as np
import pandas as pd
import plotly.graph_objects as go
from scipy.stats import binom
# =========================
# Configuração de página + estilo
# =========================
st.set_page_config(
page_title="Análise de Distribuição Binomial – Overbooking",
layout="wide",
initial_sidebar_state="expanded"
)
# Slider e detalhes em tema escuro (simples)
st.markdown(
"""
<style>
.stSlider > div > div > div > div > div > div {background-color: #C0392B !important;}
.big-title {text-align:center; color:#0d47a1;}
.sub-title {text-align:center; color:#0d47a1;}
</style>
""",
unsafe_allow_html=True,
)
# =========================
# Cabeçalho
# =========================
st.markdown("<h1 class='big-title'>Análise de Distribuição Binomial</h1>", unsafe_allow_html=True)
st.markdown("<h3 class='sub-title'>Simulação de Overbooking em Voos</h3>", unsafe_allow_html=True)
st.markdown("---")
# =========================
# Funções utilitárias
# =========================
def formatar_pct(x: float) -> str:
return f"{x*100:.2f}%"
def clamp_vendidas():
"""Garante vendidas >= capacidade sem causar 'tremedeira'."""
cap = st.session_state.capacidade
if st.session_state.vendidas < cap:
st.session_state.vendidas = cap
# =========================
# Parâmetros (no estilo do professor)
# =========================
st.markdown("### Distribuição Binomial — Simulação de Overbooking")
col1, col2, col3, col4 = st.columns(4)
with col1:
st.markdown("<h4 style='color:#0d47a1;'>Probabilidade de Comparecimento (p)</h4>", unsafe_allow_html=True)
st.slider("", min_value=0.50, max_value=0.99, value=0.88, step=0.01, key="p")
with col2:
st.markdown("<h4 style='color:#0d47a1;'>Capacidade do Avião</h4>", unsafe_allow_html=True)
st.slider("", min_value=60, max_value=300, value=120, step=1,
key="capacidade", on_change=clamp_vendidas)
with col3:
st.markdown("<h4 style='color:#0d47a1;'>Passagens Vendidas</h4>", unsafe_allow_html=True)
# intervalo amplo e independente; clamp ajusta se ficar < capacidade
st.slider("", min_value=60, max_value=380, value=130, step=1,
key="vendidas", on_change=clamp_vendidas)
with col4:
st.markdown("<h4 style='color:#0d47a1;'>Nível de Risco Aceito (%)</h4>", unsafe_allow_html=True)
st.slider("", min_value=0.01, max_value=0.30, value=0.07, step=0.01, key="limite")
# faixa da curva (capacidade até capacidade+max_extra)
st.markdown("<h4 style='color:#0d47a1;'>Faixa da Curva (capacidade + ...)</h4>", unsafe_allow_html=True)
max_extra = st.slider("", min_value=10, max_value=120, value=40, step=5)
# Valores finais
p = float(st.session_state.p)
capacidade = int(st.session_state.capacidade)
vendidas = int(max(st.session_state.vendidas, capacidade))
limite = float(st.session_state.limite)
# =========================
# Cálculos (SciPy Binomial)
# =========================
# Risco pontual: P(X > capacidade) = 1 - CDF(capacidade)
risco_pontual = 1.0 - binom.cdf(capacidade, vendidas, p)
# Curva: de capacidade até capacidade+max_extra
vendidas_range = np.arange(capacidade, capacidade + max_extra + 1)
riscos = 1.0 - binom.cdf(capacidade, vendidas_range, p)
# Tabela
tabela = pd.DataFrame(
{"Passagens vendidas": vendidas_range, "Risco de Overbooking": riscos}
)
# Máximo de vendidas respeitando o limite
ok = tabela[tabela["Risco de Overbooking"] <= limite]
max_seguro = int(ok["Passagens vendidas"].max()) if not ok.empty else None
# =========================
# Métricas
# =========================
m1, m2, m3, m4 = st.columns(4)
m1.metric("Risco atual (P>X_cap)", formatar_pct(risco_pontual))
m2.metric("Capacidade", capacidade)
m3.metric("Vendidas (ponto atual)", vendidas)
m4.metric("Máximo com risco ≤ limite", f"{max_seguro}" if max_seguro else "—")
# =========================
# Gráfico (Plotly)
# =========================
fig = go.Figure()
fig.add_trace(go.Scatter(
x=vendidas_range,
y=riscos,
mode="lines",
line=dict(color="#003366", width=3),
name="Risco de Overbooking"
))
fig.add_hline(y=limite, line_dash="dash", line_color="red", line_width=1, name="Limite")
fig.update_layout(
title=f"Risco de Overbooking para mais de {capacidade} passageiros",
xaxis_title="Passagens vendidas",
yaxis_title=f"Probabilidade de mais de {capacidade} passageiros aparecerem",
xaxis=dict(tickmode="linear"),
yaxis=dict(range=[0, 1]),
plot_bgcolor="white",
width=900,
height=420,
showlegend=False,
)
st.plotly_chart(fig, use_container_width=True)
# =========================
# Tabela
# =========================
st.write("### Tabela de Probabilidades")
st.dataframe(tabela, use_container_width=True)
if max_seguro is not None:
st.info(f"▶ Máximo de passagens com risco ≤ {formatar_pct(limite)}: **{max_seguro}**.")
else:
st.warning(f"Nenhum valor de venda dentro do limite de {formatar_pct(limite)} na faixa analisada.")