Add retained run report helpers

This commit is contained in:
2026-05-15 13:39:46 +02:00
parent cc10b78f63
commit 4d790dcdc9
6 changed files with 134 additions and 7 deletions

View File

@@ -18,7 +18,12 @@ from guide_board.planning import (
validate_assessment_profile,
validate_target_profile,
)
from guide_board.retention import build_trend_summary, list_retained_runs
from guide_board.retention import (
build_trend_summary,
list_retained_runs,
retained_run_report_paths,
select_retained_run,
)
from guide_board.schema import assert_valid
from guide_board.service import build_server
@@ -93,6 +98,17 @@ def build_parser() -> argparse.ArgumentParser:
list_runs = runs_commands.add_parser("list", help="list retained run summaries")
list_runs.add_argument("--runs-dir", type=Path)
list_runs.set_defaults(func=cmd_runs_list)
latest_run = runs_commands.add_parser("latest", help="show the latest retained run")
latest_run.add_argument("--runs-dir", type=Path)
latest_run.add_argument("--target")
latest_run.add_argument("--assessment")
latest_run.set_defaults(func=cmd_runs_latest)
report_run = runs_commands.add_parser("report", help="show report paths for a retained run")
report_run.add_argument("--runs-dir", type=Path)
report_run.add_argument("--run-id")
report_run.add_argument("--target")
report_run.add_argument("--assessment")
report_run.set_defaults(func=cmd_runs_report)
trend_runs = runs_commands.add_parser("trend", help="summarize retained run trends")
trend_runs.add_argument("--runs-dir", type=Path)
trend_runs.set_defaults(func=cmd_runs_trend)
@@ -187,6 +203,39 @@ def cmd_runs_list(args: argparse.Namespace) -> dict[str, Any]:
}
def cmd_runs_latest(args: argparse.Namespace) -> dict[str, Any]:
runs_dir = args.runs_dir or args.root / "runs"
run = select_retained_run(
runs_dir,
target_profile_ref=args.target,
assessment_profile_ref=args.assessment,
)
return {
"runs_dir": str(runs_dir),
"selection": {
"target_profile_ref": args.target,
"assessment_profile_ref": args.assessment,
},
"run": _run_with_report_paths(run) if run else None,
}
def cmd_runs_report(args: argparse.Namespace) -> dict[str, Any]:
runs_dir = args.runs_dir or args.root / "runs"
run = select_retained_run(
runs_dir,
run_id=args.run_id,
target_profile_ref=args.target,
assessment_profile_ref=args.assessment,
)
if run is None:
raise ValueError("no retained run matched the requested selection")
return {
"runs_dir": str(runs_dir),
"run": _run_with_report_paths(run),
}
def cmd_runs_trend(args: argparse.Namespace) -> dict[str, Any]:
runs_dir = args.runs_dir or args.root / "runs"
summary = build_trend_summary(runs_dir)
@@ -224,3 +273,10 @@ def _display_path(root: Path, path: Path) -> str:
return str(path.resolve().relative_to(root.resolve()))
except ValueError:
return str(path.resolve())
def _run_with_report_paths(run: dict[str, Any]) -> dict[str, Any]:
return {
**run,
"paths": retained_run_report_paths(run),
}

View File

@@ -73,6 +73,47 @@ def list_retained_runs(runs_dir: Path) -> list[dict[str, Any]]:
return sorted(summaries, key=lambda item: item.get("created_at", ""), reverse=True)
def select_retained_run(
runs_dir: Path,
run_id: str | None = None,
target_profile_ref: str | None = None,
assessment_profile_ref: str | None = None,
) -> dict[str, Any] | None:
"""Return the exact or latest retained run matching the optional selection."""
for run in list_retained_runs(runs_dir):
if run_id and run.get("run_id") != run_id:
continue
if target_profile_ref and run.get("target_profile_ref") != target_profile_ref:
continue
if assessment_profile_ref and run.get("assessment_profile_ref") != assessment_profile_ref:
continue
return run
return None
def retained_run_report_paths(run: dict[str, Any]) -> dict[str, str]:
"""Return stable report paths for a retained run summary."""
run_dir_value = run.get("run_dir")
if not isinstance(run_dir_value, str) or not run_dir_value:
raise ValueError("retained run summary is missing run_dir")
run_dir = Path(run_dir_value)
paths: dict[str, str] = {}
report_refs = run.get("report_refs", [])
if isinstance(report_refs, list):
for raw_ref in report_refs:
if not isinstance(raw_ref, str) or not raw_ref:
continue
ref = Path(raw_ref)
key = ref.stem.replace("-", "_")
paths[key] = str(run_dir / ref)
paths.setdefault("assessment_package", str(run_dir / "reports" / "assessment-package.json"))
paths.setdefault("report", str(run_dir / "reports" / "report.md"))
paths.setdefault("retention_summary", str(run_dir / "retention-summary.json"))
return dict(sorted(paths.items()))
def build_trend_summary(
runs_dir: Path,
retained_runs: list[dict[str, Any]] | None = None,