import streamlit as st import numpy as np import pandas as pd import matplotlib.pyplot as plt import seaborn as sns # --- Funções da Simulação (reutilizadas do notebook) --- def simular_viabilidade_curso(num_inscritos_sorteados, prob_confirmacao, prob_comparecimento, vagas_minimas_necessarias, num_simulacoes): """ Executa a Simulação de Monte Carlo para um dado número de inscritos sorteados. Retorna a probabilidade do curso ser confirmado. """ # Etapa 1: Simular quantos dos sorteados confirmam a vaga alunos_confirmados = np.random.binomial(n=num_inscritos_sorteados, p=prob_confirmacao, size=num_simulacoes) # Etapa 2: Dentre os confirmados, simular quantos comparecem alunos_comparecem = np.random.binomial(n=alunos_confirmados, p=prob_comparecimento, size=num_simulacoes) # Verificar em quantas simulações o curso foi confirmado sucessos = np.sum(alunos_comparecem >= vagas_minimas_necessarias) # Calcular a probabilidade prob_sucesso = sucessos / num_simulacoes return prob_sucesso # --- Interface do Streamlit --- st.title('Dashboard de Simulação de Monte Carlo para Viabilidade de Cursos') st.markdown(""" Este dashboard permite analisar a probabilidade de um curso ser confirmado com base em diversos parâmetros. Utilize os controles na barra lateral para ajustar os cenários e observar o impacto nos resultados. """) # --- Barra Lateral com os Parâmetros de Entrada --- st.sidebar.title('Parâmetros de Entrada') st.sidebar.header('Parâmetros do Curso') total_vagas = st.sidebar.slider('Total de Vagas', min_value=10, max_value=200, value=50, step=5) min_ocupacao_perc = st.sidebar.slider('Ocupação Mínima para Confirmação (%)', min_value=50, max_value=100, value=90, step=5) min_ocupacao_perc = min_ocupacao_perc / 100.0 vagas_minimas_necessarias = int(total_vagas * min_ocupacao_perc) st.sidebar.header('Probabilidades do Cenário') prob_confirmacao = st.sidebar.slider('Probabilidade de Confirmação de Vaga (%)', min_value=50, max_value=100, value=85, step=1) prob_confirmacao = prob_confirmacao / 100.0 prob_comparecimento = st.sidebar.slider('Probabilidade de Comparecimento (%)', min_value=50, max_value=100, value=95, step=1) prob_comparecimento = prob_comparecimento / 100.0 st.sidebar.header('Parâmetros da Simulação') num_simulacoes = st.sidebar.select_slider('Número de Simulações (rodadas)', options=[1000, 5000, 10000, 20000], value=10000) min_inscritos_teste = st.sidebar.number_input('Mínimo de Inscritos a Testar', value=int(total_vagas * 0.1), min_value=1) max_inscritos_teste = st.sidebar.number_input('Máximo de Inscritos a Testar', value=total_vagas * 2, min_value=int(min_inscritos_teste)+10) # --- Execução da Simulação com base nos inputs --- if st.sidebar.button('Executar Simulação'): with st.spinner('Realizando simulações... Por favor, aguarde.'): lista_de_inscritos = range(min_inscritos_teste, max_inscritos_teste + 1) resultados_simulacao = [] for num_convocados in lista_de_inscritos: probabilidade = simular_viabilidade_curso(num_convocados, prob_confirmacao, prob_comparecimento, vagas_minimas_necessarias, num_simulacoes) resultados_simulacao.append({'num_convocados': num_convocados, 'prob_confirmacao_curso': probabilidade}) df_resultados = pd.DataFrame(resultados_simulacao) st.success('Simulação concluída!') # --- Exibição dos Resultados --- st.header('Resultados da Simulação') # Análise para encontrar os limiares try: convocados_min_req = df_resultados[df_resultados['prob_confirmacao_curso'] >= min_ocupacao_perc].iloc[0] convocados_99 = df_resultados[df_resultados['prob_confirmacao_curso'] >= 0.99].iloc[0] except IndexError: st.warning("Não foi possível atingir os limiares de probabilidade na faixa de testes. Tente aumentar o 'Máximo de Inscritos a Testar'.") convocados_min_req = None convocados_99 = None col1, col2 = st.columns(2) if convocados_min_req is not None: col1.metric(f"Mínimo para {int(min_ocupacao_perc*100)}% de chance", f"{int(convocados_min_req['num_convocados'])} convocados") if convocados_99 is not None: col2.metric("Mínimo para 99% de chance", f"{int(convocados_99['num_convocados'])} convocados") # Gráfico de Distribuição Binomial st.subheader('Análise da Distribuição de Alunos') st.markdown("Distribuição das probabilidades de sucesso para o número de convocados dentro do total de vagas disponíveis") num_conv_dist = total_vagas confirmados_dist = np.random.binomial(n=num_conv_dist, p=prob_confirmacao, size=num_simulacoes) comparecem_dist = np.random.binomial(n=confirmados_dist, p=prob_comparecimento, size=num_simulacoes) valor_esperado = num_conv_dist * prob_confirmacao * prob_comparecimento fig2, ax2 = plt.subplots(figsize=(14, 7)) sns.histplot(comparecem_dist, kde=True, stat="probability", discrete=True, ax=ax2) ax2.axvline(valor_esperado, color='red', linestyle='--', linewidth=2, label=f'Valor Esperado: {valor_esperado:.2f}') ax2.axvline(vagas_minimas_necessarias, color='purple', linestyle=':', linewidth=2, label=f'Mínimo Necessário: {vagas_minimas_necessarias}') ax2.set_title(f'Distribuição Simulada de Comparecimento para {num_conv_dist} Convocados', fontsize=16) ax2.set_xlabel(f'Número de Alunos que Comparecem', fontsize=12) ax2.set_ylabel('Probabilidade (Frequência Relativa)', fontsize=12) ax2.legend() st.pyplot(fig2) # Gráfico Principal st.subheader('Probabilidade de Confirmação vs. Número de Convocados') fig1, ax1 = plt.subplots(figsize=(14, 7)) sns.set_style("whitegrid") ax1.plot(df_resultados['num_convocados'], df_resultados['prob_confirmacao_curso'], marker='o', linestyle='-', label='Probabilidade de Confirmação do Curso') ax1.axhline(y=min_ocupacao_perc, color='g', linestyle='--', label=f"Limiar de {int(min_ocupacao_perc*100)}%") ax1.axhline(y=0.99, color='r', linestyle='--', label='Limiar de 99%') if convocados_min_req is not None: ax1.axvline(x=convocados_min_req['num_convocados'], color='g', linestyle='--', alpha=0.7) ax1.text(convocados_min_req['num_convocados'] + 0.5, 0.5, f"{int(convocados_min_req['num_convocados'])} convocados\npara {int(min_ocupacao_perc*100)}%", color='g') if convocados_99 is not None: ax1.axvline(x=convocados_99['num_convocados'], color='r', linestyle='--', alpha=0.7) ax1.text(convocados_99['num_convocados'] + 0.5, 0.65, f"{int(convocados_99['num_convocados'])} convocados\npara 99%", color='r') ax1.set_title('Probabilidade de Confirmação do Curso vs. Número de Convocados', fontsize=16) ax1.set_xlabel('Número de Pessoas Convocadas (Sorteados)', fontsize=12) ax1.set_ylabel('Probabilidade de Confirmação do Curso', fontsize=12) ax1.legend() ax1.grid(True) ax1.set_ylim(0, 1.05) ax1.set_xlim(min(lista_de_inscritos), max(lista_de_inscritos)) st.pyplot(fig1) # Tabela de Resultados st.subheader('Tabela de Resultados Detalhados') st.dataframe(df_resultados.style.format({'prob_confirmacao_curso': '{:.2%}'})) else: st.info('Ajuste os parâmetros na barra lateral e clique em "Executar Simulação" para começar.')