generated from coulomb/repo-seed
Add retained run report helpers
This commit is contained in:
@@ -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),
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user