Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 84 additions & 24 deletions apps/analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import html
import json
import os
import re
from pathlib import Path

import pandas as pd
Expand Down Expand Up @@ -101,6 +102,28 @@ def get_run_directories(output_dir: Path) -> list[Path]:
return sorted(run_dirs, key=lambda d: d.name, reverse=True)


def _system_name_from_run(run_dir: Path) -> str:
"""Extract the system name from a run folder name (<timestamp>_<system_name>)."""
m = re.match(r"^\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2}\.\d+_(.+)$", run_dir.name)
return m.group(1) if m else run_dir.name


def filter_latest_runs(run_dirs: list[Path]) -> list[Path]:
"""Keep only the most recent run per system name.

Assumes run_dirs is already sorted newest-first (as returned by
get_run_directories), so the first occurrence of each system name wins.
"""
seen: set[str] = set()
result = []
for d in run_dirs:
system = _system_name_from_run(d)
if system not in seen:
seen.add(system)
result.append(d)
return result


def get_record_directories(run_dir: Path) -> list[Path]:
"""Get all record directories in a run, sorted by record ID."""
records_dir = run_dir / "records"
Expand Down Expand Up @@ -448,12 +471,24 @@ def _model_suffix_from_config(run_config: dict) -> str:
return "_".join(p for p in parts if p)


def _get_run_label(run_name: str, run_config: dict) -> str:
"""Build a display label for a run, appending model info if not already in the name."""
_TIMESTAMP_RUN_RE = re.compile(r"^(\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2}\.\d+)_(.+)$")


def _get_system_and_timestamp(run_name: str, run_config: dict) -> tuple[str, str]:
"""Return (system_name, timestamp) for a run."""
m = _TIMESTAMP_RUN_RE.match(run_name)
if m:
return m.group(2), m.group(1)
suffix = _model_suffix_from_config(run_config)
if not suffix or suffix in run_name:
return run_name
return f"{run_name} ({suffix})"
if suffix and suffix not in run_name:
return f"{suffix} ({run_name})", ""
return run_name, ""


def _get_run_label(run_name: str, run_config: dict) -> str:
"""Build a display label for a run (used for chart legends)."""
system, timestamp = _get_system_and_timestamp(run_name, run_config)
return f"{system} ({timestamp})" if timestamp else system


def _color_cell(val):
Expand Down Expand Up @@ -943,9 +978,13 @@ def render_cross_run_comparison(run_dirs: list[Path], output_dir_str: str = ""):
metric_names = list(per_metric.keys())
all_metric_names.update(metric_names)
model_details = _extract_model_details(run_config)
system_name, run_timestamp = _get_system_and_timestamp(run_name, run_config)
summary: dict = {
"run": run_name,
"run_output_dir": str(run_dir.parent),
"label": _get_run_label(run_name, run_config),
"system_name": system_name,
"run_timestamp": run_timestamp,
"records": metrics_summary.get("total_records", 0),
"pipeline_type": _classify_pipeline_type(run_config),
**model_details,
Expand All @@ -967,9 +1006,13 @@ def render_cross_run_comparison(run_dirs: list[Path], output_dir_str: str = ""):
all_metric_names.update(metric_names)
df = pd.DataFrame(rows)
model_details = _extract_model_details(run_config)
system_name, run_timestamp = _get_system_and_timestamp(run_name, run_config)
summary = {
"run": run_name,
"run_output_dir": str(run_dir.parent),
"label": _get_run_label(run_name, run_config),
"system_name": system_name,
"run_timestamp": run_timestamp,
"records": len(df),
"pipeline_type": _classify_pipeline_type(run_config),
**model_details,
Expand Down Expand Up @@ -1030,19 +1073,26 @@ def render_cross_run_comparison(run_dirs: list[Path], output_dir_str: str = ""):

# Metrics table: EVA composites first, then all individual metrics
table_composites = [c for c in _EVA_BAR_COMPOSITES if c in summary_df.columns]
display_cols = ["label", "records"] + table_composites + ordered_metrics
display_cols = ["system_name", "run_timestamp", "records"] + table_composites + ordered_metrics
display_df = summary_df[display_cols].copy()

# Add link column to navigate to Run Overview
output_dir_str = str(run_dirs[0].parent) if run_dirs else ""
# Add link column to navigate to Run Overview (use per-run output dir to support multiple output dirs)
display_df.insert(
0,
"link",
summary_df["run"].apply(lambda r: f"?output_dir={output_dir_str}&view=Run+Overview&run={r}"),
summary_df.apply(lambda row: f"?output_dir={row['run_output_dir']}&view=Run+Overview&run={row['run']}", axis=1),
)

composite_rename = {c: f"[EVA] {_EVA_COMPOSITE_DISPLAY[c]}" for c in table_composites}
display_df = display_df.rename(columns={"label": "Run", "records": "# Records", **composite_rename, **col_rename})
display_df = display_df.rename(
columns={
"system_name": "System",
"run_timestamp": "Timestamp",
"records": "# Records",
**composite_rename,
**col_rename,
}
)
renamed_composites = [composite_rename[c] for c in table_composites]
renamed_metrics = [col_rename[m] for m in ordered_metrics]
all_score_cols = renamed_composites + renamed_metrics
Expand Down Expand Up @@ -1752,14 +1802,22 @@ def main():
query_params = st.query_params

# Sidebar: output directory selection
st.sidebar.header("Output Directory")
st.sidebar.header("Output Directories")
default_output = query_params.get("output_dir", _DEFAULT_OUTPUT_DIR)
output_dir = Path(st.sidebar.text_input("Path to output directory", value=default_output))
# Normalize comma-separated (from URL query params) to newline-separated (for text area)
if "," in default_output and "\n" not in default_output:
default_output = "\n".join(p.strip() for p in default_output.split(","))
output_dirs_input = st.sidebar.text_area("Paths to output directories (one per line)", value=default_output)
output_dirs = [Path(p.strip()) for p in output_dirs_input.splitlines() if p.strip()] or [Path(_DEFAULT_OUTPUT_DIR)]

run_dirs = [rd for od in output_dirs for rd in get_run_directories(od)]

run_dirs = get_run_directories(output_dir)
latest_only = st.sidebar.toggle("Latest run per system only", value=True)
if latest_only:
run_dirs = filter_latest_runs(run_dirs)

if not run_dirs:
st.error(f"No run directories found in {output_dir}")
st.error(f"No run directories found in: {', '.join(str(d) for d in output_dirs)}")
return

# View mode
Expand All @@ -1770,17 +1828,19 @@ def main():
view_mode = st.sidebar.radio("View", view_options, index=default_view_idx, label_visibility="collapsed")

if view_mode == "Cross-Run Comparison":
render_cross_run_comparison([output_dir / d.name for d in run_dirs], str(output_dir))
render_cross_run_comparison(run_dirs, ", ".join(str(d) for d in output_dirs))
return

# Sidebar: run selection
st.sidebar.header("Run Selection")
run_dir_names = [d.name for d in run_dirs]
multiple_output_dirs = len(output_dirs) > 1
run_dir_labels = [str(d) if multiple_output_dirs else d.name for d in run_dirs]
default_run_idx = 0
if "run" in query_params and query_params["run"] in run_dir_names:
default_run_idx = run_dir_names.index(query_params["run"])
selected_run_name = st.sidebar.selectbox("Select Run", run_dir_names, index=default_run_idx)
selected_run_dir = output_dir / selected_run_name
if "run" in query_params and query_params["run"] in run_dir_labels:
default_run_idx = run_dir_labels.index(query_params["run"])
selected_run_label = st.sidebar.selectbox("Select Run", run_dir_labels, index=default_run_idx)
selected_run_dir = run_dirs[run_dir_labels.index(selected_run_label)]
selected_run_name = selected_run_dir.name

run_config = _load_run_config(selected_run_dir)
if run_config:
Expand All @@ -1791,9 +1851,9 @@ def main():
if view_mode == "Run Overview":
st.query_params.from_dict(
{
"output_dir": str(output_dir),
"output_dir": ",".join(str(d) for d in output_dirs),
"view": "Run Overview",
"run": selected_run_name,
"run": selected_run_label,
}
)
render_run_overview(selected_run_dir)
Expand Down Expand Up @@ -1840,9 +1900,9 @@ def main():

# Update query params for deep linking
new_params = {
"output_dir": str(output_dir),
"output_dir": ",".join(str(d) for d in output_dirs),
"view": "Record Detail",
"run": selected_run_name,
"run": selected_run_label,
"record": selected_record_name,
}
if selected_trial:
Expand Down
Loading