tonylopes's picture
Update app.py
522f3a8 verified
# 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()