diff --git a/src/multi_swarm/dashboard/__init__.py b/src/multi_swarm/dashboard/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/multi_swarm/dashboard/data.py b/src/multi_swarm/dashboard/data.py new file mode 100644 index 0000000..3abfe15 --- /dev/null +++ b/src/multi_swarm/dashboard/data.py @@ -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) diff --git a/src/multi_swarm/dashboard/pages/01_overview.py b/src/multi_swarm/dashboard/pages/01_overview.py new file mode 100644 index 0000000..a753eb0 --- /dev/null +++ b/src/multi_swarm/dashboard/pages/01_overview.py @@ -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"]) diff --git a/src/multi_swarm/dashboard/streamlit_app.py b/src/multi_swarm/dashboard/streamlit_app.py new file mode 100644 index 0000000..1ddfff4 --- /dev/null +++ b/src/multi_swarm/dashboard/streamlit_app.py @@ -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()}`") diff --git a/tests/integration/test_streamlit_smoke.py b/tests/integration/test_streamlit_smoke.py new file mode 100644 index 0000000..d3425e5 --- /dev/null +++ b/tests/integration/test_streamlit_smoke.py @@ -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")