feat(dashboard): aquarium click handler with info panel + ancestor lineage
Rimuove sidebar acquario (slider max-pesci, toggle label): la dimensione popolazione è già definita dal GA, le label sono ridondanti col pannello di ispezione. Mostra tutti i pesci della generazione selezionata. Aggiunge `build_lineage_index` (mappa ogni genome_id della run ai suoi attributi) e `trace_ancestors` (BFS sui parent_ids fino a max_levels, guardia su cicli). `build_fish_dataset` accetta ora il lineage_index e allega il campo `ancestors` ad ogni pesce; conserva la firma legacy per compat con i fixture di test esistenti. `build_aquarium_html` perde `show_labels`. Embedda click handler con hit-test in canvas pixel space (account per CSS scaling) + pannello info top-right con stile, fitness/DSR/Sharpe/maxDD/trades, prompt e albero discendenza colorato per cognitive_style. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -7,10 +7,10 @@ from multi_swarm.dashboard.aquarium import (
|
||||
STYLE_COLORS,
|
||||
build_aquarium_html,
|
||||
build_fish_dataset,
|
||||
build_lineage_index,
|
||||
)
|
||||
from multi_swarm.dashboard.data import (
|
||||
evaluations_df,
|
||||
generations_df,
|
||||
genomes_df,
|
||||
get_repo,
|
||||
list_runs_df,
|
||||
@@ -18,8 +18,8 @@ from multi_swarm.dashboard.data import (
|
||||
|
||||
st.title("Aquarium 2D")
|
||||
st.caption(
|
||||
"Ogni genoma è un pesce: dimensione proporzionale alla fitness, "
|
||||
"colore per cognitive_style."
|
||||
"Pesci colorati per stile cognitivo, dimensione proporzionale a fitness. "
|
||||
"Click su un pesce per dettaglio + discendenza."
|
||||
)
|
||||
|
||||
db_path = st.session_state.get("db_path", "./runs.db")
|
||||
@@ -27,52 +27,55 @@ repo = get_repo(db_path)
|
||||
|
||||
runs = list_runs_df(repo)
|
||||
if runs.empty:
|
||||
st.info("Nessuna run.")
|
||||
st.info("Nessuna run nel database.")
|
||||
st.stop()
|
||||
|
||||
selected = st.selectbox("Run", runs["id"].tolist())
|
||||
selected_run = st.selectbox("Run", runs["id"].tolist())
|
||||
|
||||
gens = generations_df(repo, selected)
|
||||
gen_options: list[int | None] = [None]
|
||||
if not gens.empty:
|
||||
gen_options.extend(sorted(gens["generation_idx"].unique().tolist()))
|
||||
# Fetch ALL genomes of the run (no gen filter): needed to build the lineage
|
||||
# index across generations. The active set is filtered afterwards.
|
||||
all_genomes = genomes_df(repo, selected_run)
|
||||
all_evals = evaluations_df(repo, selected_run)
|
||||
|
||||
def _fmt_gen(v: int | None) -> str:
|
||||
return "tutte le generazioni" if v is None else f"gen {v}"
|
||||
|
||||
# Default to latest gen if available.
|
||||
default_idx = len(gen_options) - 1 if len(gen_options) > 1 else 0
|
||||
gen_choice = st.selectbox(
|
||||
"Generazione", gen_options, index=default_idx, format_func=_fmt_gen
|
||||
)
|
||||
|
||||
with st.sidebar:
|
||||
st.subheader("Aquarium controls")
|
||||
max_fish = st.slider("Max pesci", min_value=5, max_value=100, value=30, step=1)
|
||||
show_labels = st.toggle("Mostra ID genoma", value=False)
|
||||
if st.button("Refresh"):
|
||||
st.rerun()
|
||||
|
||||
evals = evaluations_df(repo, selected)
|
||||
genomes = genomes_df(repo, selected, generation_idx=gen_choice)
|
||||
|
||||
if evals.empty or genomes.empty:
|
||||
st.warning("Nessun dato disponibile per questa run/generazione.")
|
||||
if all_genomes.empty:
|
||||
st.warning("Nessun genoma per questa run.")
|
||||
st.stop()
|
||||
|
||||
merged = evals.merge(
|
||||
genomes, left_on="genome_id", right_on="id", how="inner", suffixes=("", "_g")
|
||||
available_gens = sorted(all_genomes["generation_idx"].unique().tolist())
|
||||
selected_gen = st.selectbox(
|
||||
"Generazione",
|
||||
available_gens,
|
||||
index=len(available_gens) - 1, # default ultima
|
||||
)
|
||||
if merged.empty:
|
||||
st.warning("Nessuna evaluation associata ai genomi della generazione scelta.")
|
||||
|
||||
active_genomes = all_genomes[all_genomes["generation_idx"] == selected_gen]
|
||||
active_evals = (
|
||||
all_evals[all_evals["genome_id"].isin(active_genomes["id"])]
|
||||
if not all_evals.empty
|
||||
else all_evals
|
||||
)
|
||||
if not active_evals.empty:
|
||||
active_merged = active_genomes.merge(
|
||||
active_evals,
|
||||
left_on="id",
|
||||
right_on="genome_id",
|
||||
how="left",
|
||||
suffixes=("", "_eval"),
|
||||
)
|
||||
else:
|
||||
active_merged = active_genomes.copy()
|
||||
active_merged["genome_id"] = active_merged["id"]
|
||||
|
||||
lineage = build_lineage_index(all_genomes, all_evals)
|
||||
fish = build_fish_dataset(active_merged, lineage, max_lineage_levels=5)
|
||||
|
||||
if not fish:
|
||||
st.warning("Nessun agente attivo in questa generazione.")
|
||||
st.stop()
|
||||
|
||||
fish = build_fish_dataset(merged, max_fish=max_fish)
|
||||
st.write(f"Visualizzati {len(fish)} pesci (top per fitness).")
|
||||
st.caption(f"{len(fish)} agenti in generazione {selected_gen}")
|
||||
|
||||
html_str = build_aquarium_html(
|
||||
fish, canvas_w=1000, canvas_h=600, show_labels=show_labels
|
||||
)
|
||||
html_str = build_aquarium_html(fish, canvas_w=1000, canvas_h=600)
|
||||
components.html(html_str, height=620, scrolling=False)
|
||||
|
||||
with st.expander("Legenda colori"):
|
||||
|
||||
Reference in New Issue
Block a user