feat(dashboard): progress bar live + top fitness sulla pagina Overview

Aggiunto blocco "Progresso run" sopra le metriche statiche con:
- progress bar generazioni (gens_done / n_generations)
- progress bar evaluations (evals_done / pop × gen) con percentuale
- metric top fitness / median fitness / cost so far
- pulsante Refresh manuale + timestamp ultimo update
- emoji status (🟢 running /  completed /  failed)

Niente nuove dipendenze: solo st.progress + st.rerun standard Streamlit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-11 22:21:04 +02:00
parent 41e26cbe5b
commit 7b790b1bc3
+38 -1
View File
@@ -1,9 +1,12 @@
from __future__ import annotations from __future__ import annotations
from datetime import datetime
import streamlit as st import streamlit as st
from multi_swarm.dashboard.data import ( from multi_swarm.dashboard.data import (
evaluations_df, evaluations_df,
generations_df,
get_repo, get_repo,
get_run_overview, get_run_overview,
list_runs_df, list_runs_df,
@@ -25,6 +28,41 @@ st.dataframe(runs[["id", "name", "started_at", "completed_at", "status", "total_
selected = st.selectbox("Seleziona run per dettaglio", runs["id"].tolist()) selected = st.selectbox("Seleziona run per dettaglio", runs["id"].tolist())
overview = get_run_overview(repo, selected) overview = get_run_overview(repo, selected)
# --- Progress live ---
cfg = overview["config"]
pop_size = int(cfg.get("population_size", 0))
n_gens = int(cfg.get("n_generations", 0))
evals = evaluations_df(repo, selected)
gens = generations_df(repo, selected)
evals_done = len(evals)
evals_total = max(pop_size * n_gens, 1)
gens_done = int(gens["completed_at"].notna().sum()) if not gens.empty else 0
status_emoji = {"running": "🟢", "completed": "", "failed": ""}.get(overview["status"], "")
top_fit = float(evals["fitness"].max()) if not evals.empty else float("nan")
st.subheader(f"{status_emoji} Progresso run")
st.progress(
min(gens_done / max(n_gens, 1), 1.0),
text=f"Generations: {gens_done}/{n_gens}",
)
st.progress(
min(evals_done / evals_total, 1.0),
text=f"Evaluations: {evals_done}/{evals_total} ({100*evals_done/evals_total:.1f}%)",
)
pcol1, pcol2, pcol3 = st.columns(3)
pcol1.metric("Top fitness", f"{top_fit:.4f}" if evals_done else "")
pcol2.metric("Median fitness", f"{evals['fitness'].median():.4f}" if evals_done else "")
pcol3.metric("Cost so far", f"${overview['total_cost_usd']:.4f}")
ref_col1, ref_col2 = st.columns([1, 4])
if ref_col1.button("🔄 Refresh"):
st.rerun()
ref_col2.caption(f"Last update: {datetime.now().strftime('%H:%M:%S')}")
st.divider()
col1, col2, col3, col4 = st.columns(4) col1, col2, col3, col4 = st.columns(4)
col1.metric("Status", overview["status"]) col1.metric("Status", overview["status"])
col2.metric("Cost (USD)", f"{overview['total_cost_usd']:.4f}") col2.metric("Cost (USD)", f"{overview['total_cost_usd']:.4f}")
@@ -32,7 +70,6 @@ col3.metric("Started", overview["started_at"])
col4.metric("Completed", overview["completed_at"] or "") col4.metric("Completed", overview["completed_at"] or "")
st.subheader("Statistiche evaluations") st.subheader("Statistiche evaluations")
evals = evaluations_df(repo, selected)
col5, col6, col7, col8 = st.columns(4) col5, col6, col7, col8 = st.columns(4)
if not evals.empty: if not evals.empty:
parse_success = 100 * (evals["parse_error"].isna().sum() / len(evals)) parse_success = 100 * (evals["parse_error"].isna().sum() / len(evals))