# app.py import gradio as gr import numpy as np import matplotlib.pyplot as plt import seaborn as sns from scipy import stats import io # --- Funções do seu script original --- # (com uma pequena modificação na função de gráfico) def simular_sprints(backlog: int, lam: float, n_sim: int, rng) -> np.ndarray: """Devolve o n de sprints para zerar backlog em cada simulação.""" # Estima um número máximo de sprints razoável para evitar loops infinitos # Aumenta a folga para casos de lambda muito baixo if lam > 0: max_sprints = int(np.ceil(backlog / (lam * 0.2))) + 20 else: max_sprints = 200 # Um fallback caso lambda seja zero sprints_simuladas = rng.poisson(lam, size=(n_sim, max_sprints)) sprints_acumuladas = sprints_simuladas.cumsum(axis=1) # +1 porque o índice é 0-based e sprint é 1-based sprints_para_concluir = np.argmax(sprints_acumuladas >= backlog, axis=1) + 1 # Se alguma simulação não atingir o backlog, np.argmax retorna 0. # Corrigimos isso para indicar que não foi concluído no tempo simulado. nao_concluido = sprints_acumuladas[:, -1] < backlog sprints_para_concluir[nao_concluido] = max_sprints # Marcamos como o máximo return sprints_para_concluir def resumo_percentis(arr: np.ndarray, pcts: tuple = (50, 85, 95)) -> dict[str, int]: """Calcula os percentis dos resultados da simulação.""" return {f"{p}%": int(np.ceil(np.percentile(arr, p))) for p in pcts} def criar_grafico(arr: np.ndarray, nome_orgao: str, percentis: dict) -> plt.Figure: """Cria e retorna a figura do histograma.""" sns.set_theme(style="whitegrid") fig, ax = plt.subplots() # Cria figura e eixos sns.histplot(arr, bins="auto", stat="probability", ax=ax, color="#4C72B0", edgecolor=".2") for p_str, v in percentis.items(): ax.axvline(v, ls='--', lw=1.2, label=f"{p_str} de confiança: {v} sprints") ax.set(title=f"Distribuição de Sprints para Concluir o Backlog de {nome_orgao}", xlabel="Número de Sprints", ylabel="Probabilidade") ax.legend() plt.tight_layout() # IMPORTANTE: Em vez de plt.show(), retornamos a figura return fig # --- Função principal que integra tudo com a interface --- def rodar_simulacao(nome_orgao, backlog_str, historico_str, n_sim_str): """ Função chamada pela interface Gradio. Recebe os inputs, roda a simulação e retorna os outputs formatados. """ try: # 1. Validar e converter inputs backlog = int(backlog_str) n_sim = int(n_sim_str) historico_numeros = [float(x.strip()) for x in historico_str.split(',')] historico = np.array(historico_numeros) if backlog <= 0 or n_sim <= 0: raise ValueError("Backlog e Nº de Simulações devem ser positivos.") if historico.size == 0 or np.mean(historico) <= 0: raise ValueError("O histórico de produtividade deve ser válido e a média maior que zero.") # 2. Executar a simulação RNG = np.random.default_rng(42) # Para reprodutibilidade lam = historico.mean() sims = simular_sprints(backlog, lam, n_sim, RNG) # 3. Gerar resumo e gráfico percentis = resumo_percentis(sims) texto_percentis = f"Resultados para {nome_orgao} (Lambda = {lam:.2f}):\n" for p, v in percentis.items(): texto_percentis += f"- {p} de probabilidade de concluir em {v} sprints ou menos.\n" figura_plot = criar_grafico(sims, nome_orgao, percentis) # 4. Análise de Sensibilidade texto_sensibilidade = "--- Análise de Sensibilidade (Prazo para 85% de confiança) ---\n" for delta in [+0.10, -0.10]: lam_alt = lam * (1 + delta) sims_alt = simular_sprints(backlog, lam_alt, n_sim, RNG) p85 = int(np.ceil(np.percentile(sims_alt, 85))) texto_sensibilidade += f"Com variação de {delta:+.0%}, o prazo vai para {p85} sprints (λ={lam_alt:.2f}).\n" return texto_percentis, figura_plot, texto_sensibilidade except Exception as e: # Retorna uma mensagem de erro amigável se algo der errado error_message = f"Ocorreu um erro: {e}. Por favor, verifique os valores de entrada." # Cria um plot vazio para não quebrar a interface fig_vazia, ax = plt.subplots() ax.text(0.5, 0.5, 'Erro na Geração do Gráfico', ha='center', va='center') return error_message, fig_vazia, "" # --- Definição da Interface Gradio --- with gr.Blocks(theme=gr.themes.Soft()) as demo: gr.Markdown("# 🎲 Simulador de Monte Carlo para Previsão de Projetos Ágeis") gr.Markdown( "Esta é uma demonstração interativa do modelo descrito no meu trabalho da matéria ANÁLISE ESTATÍSTICA DE DADOS E INFORMAÇÕES, no mestrado em computação aplicada. " ) gr.Markdown( "O funcionamento é relativamente simples, você insere o deparamento, a quantidade de Produtos de Itens de Backlog(PBI) e a quantidade de tarefas por sprints." "Feito isso, basta pedir para que o algoritmo preveja(Rodar Simulação) o tempo que será gasto para a conclusão do projeto." ) with gr.Row(): with gr.Column(scale=1): gr.Markdown("### 1. Dados de Entrada") orgao = gr.Textbox(label="Nome do Projeto ou Equipe", value="PGCONT") backlog_input = gr.Textbox(label="Tamanho do Backlog (nº de tarefas)", value="176") historico_input = gr.Textbox( label="Produtividade Histórica (tarefas por sprint, separadas por vírgula)", value="7, 11, 9, 5, 13" ) nsim_input = gr.Textbox(label="Número de Simulações", value="10000") btn = gr.Button("Rodar Simulação", variant="primary") with gr.Column(scale=2): gr.Markdown("### 2. Resultados da Previsão") texto_output = gr.Textbox(label="Percentis de Conclusão", lines=5) plot_output = gr.Plot(label="Distribuição dos Prazos (em Sprints)") sensibilidade_output = gr.Textbox(label="Análise de Sensibilidade", lines=4) btn.click( fn=rodar_simulacao, inputs=[orgao, backlog_input, historico_input, nsim_input], outputs=[texto_output, plot_output, sensibilidade_output] ) gr.Examples( [["PGFAZ", "13", "3, 3, 5, 1, 2", "10000"]], inputs=[orgao, backlog_input, historico_input, nsim_input] ) demo.launch()