feat(dashboard): streamlit skeleton + Overview page + data layer
Aggiunge scheletro multipage Streamlit per Phase 1: - modulo data.py con helper (list_runs_df, get_run_overview, generations_df, evaluations_df, genomes_df) sopra Repository. - streamlit_app.py entry point con DB_PATH da env. - pages/01_overview.py per elenco run + metriche + config JSON. - smoke test import di multi_swarm.dashboard.data. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,54 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
import pandas as pd # type: ignore[import-untyped]
|
||||||
|
|
||||||
|
from ..persistence.repository import Repository
|
||||||
|
|
||||||
|
|
||||||
|
def get_repo(db_path: str | Path) -> Repository:
|
||||||
|
return Repository(db_path=db_path)
|
||||||
|
|
||||||
|
|
||||||
|
def list_runs_df(repo: Repository) -> pd.DataFrame:
|
||||||
|
return pd.DataFrame(repo.list_runs())
|
||||||
|
|
||||||
|
|
||||||
|
def get_run_overview(repo: Repository, run_id: str) -> dict[str, Any]:
|
||||||
|
run = repo.get_run(run_id)
|
||||||
|
return {
|
||||||
|
"name": run["name"],
|
||||||
|
"started_at": run["started_at"],
|
||||||
|
"completed_at": run["completed_at"],
|
||||||
|
"status": run["status"],
|
||||||
|
"total_cost_usd": run["total_cost_usd"],
|
||||||
|
"config": json.loads(run["config_json"]),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def generations_df(repo: Repository, run_id: str) -> pd.DataFrame:
|
||||||
|
return pd.DataFrame(repo.list_generations(run_id))
|
||||||
|
|
||||||
|
|
||||||
|
def evaluations_df(repo: Repository, run_id: str) -> pd.DataFrame:
|
||||||
|
return pd.DataFrame(repo.list_evaluations(run_id))
|
||||||
|
|
||||||
|
|
||||||
|
def genomes_df(
|
||||||
|
repo: Repository, run_id: str, generation_idx: int | None = None
|
||||||
|
) -> pd.DataFrame:
|
||||||
|
rows = repo.list_genomes(run_id, generation_idx)
|
||||||
|
flat: list[dict[str, Any]] = []
|
||||||
|
for r in rows:
|
||||||
|
payload = json.loads(r["payload_json"])
|
||||||
|
flat.append(
|
||||||
|
{
|
||||||
|
"id": r["id"],
|
||||||
|
"generation_idx": r["generation_idx"],
|
||||||
|
**payload,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return pd.DataFrame(flat)
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import streamlit as st
|
||||||
|
|
||||||
|
from multi_swarm.dashboard.data import get_repo, get_run_overview, list_runs_df
|
||||||
|
|
||||||
|
st.title("Overview")
|
||||||
|
|
||||||
|
db_path = st.session_state.get("db_path", "./runs.db")
|
||||||
|
repo = get_repo(db_path)
|
||||||
|
|
||||||
|
runs = list_runs_df(repo)
|
||||||
|
if runs.empty:
|
||||||
|
st.info("Nessuna run nel database. Esegui `scripts/run_phase1.py` per generarne una.")
|
||||||
|
st.stop()
|
||||||
|
|
||||||
|
st.subheader("Tutte le run")
|
||||||
|
st.dataframe(runs[["id", "name", "started_at", "completed_at", "status", "total_cost_usd"]])
|
||||||
|
|
||||||
|
selected = st.selectbox("Seleziona run per dettaglio", runs["id"].tolist())
|
||||||
|
overview = get_run_overview(repo, selected)
|
||||||
|
|
||||||
|
col1, col2, col3, col4 = st.columns(4)
|
||||||
|
col1.metric("Status", overview["status"])
|
||||||
|
col2.metric("Cost (USD)", f"{overview['total_cost_usd']:.4f}")
|
||||||
|
col3.metric("Started", overview["started_at"])
|
||||||
|
col4.metric("Completed", overview["completed_at"] or "—")
|
||||||
|
|
||||||
|
st.subheader("Config")
|
||||||
|
st.json(overview["config"])
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import streamlit as st
|
||||||
|
|
||||||
|
st.set_page_config(page_title="Multi-Swarm Phase 1", layout="wide")
|
||||||
|
st.title("Multi-Swarm Coevolutivo — Phase 1 dashboard")
|
||||||
|
st.markdown(
|
||||||
|
"""
|
||||||
|
Naviga le pagine nel menu a sinistra:
|
||||||
|
- **Overview**: ultima run e stato globale.
|
||||||
|
- **GA Convergence**: fitness per generazione.
|
||||||
|
- **Genomes**: top-K genomi e ispezione qualitativa.
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
db_path = os.environ.get("DB_PATH", "./runs.db")
|
||||||
|
st.session_state["db_path"] = db_path
|
||||||
|
st.caption(f"DB path: `{Path(db_path).resolve()}`")
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import importlib
|
||||||
|
|
||||||
|
|
||||||
|
def test_streamlit_app_imports():
|
||||||
|
importlib.import_module("multi_swarm.dashboard.data")
|
||||||
|
|
||||||
|
|
||||||
|
def test_dashboard_data_helpers_signatures():
|
||||||
|
from multi_swarm.dashboard import data
|
||||||
|
|
||||||
|
assert hasattr(data, "list_runs_df")
|
||||||
|
assert hasattr(data, "generations_df")
|
||||||
|
assert hasattr(data, "evaluations_df")
|
||||||
|
assert hasattr(data, "genomes_df")
|
||||||
Reference in New Issue
Block a user