Spaces:
Sleeping
Sleeping
| #!/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.") | |