Active user attribute set, latest activities, guarded changes to used values, artefact links, durations and averages; irreversible showcase step
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Assistant: claude-code Assistant-Model: sonnet Assistant-Process: 295952@bnt-lap001 Assistant-Session: e93f64ad-516c-46eb-9666-aad8d300c477
This commit is contained in:
parent
6716cee921
commit
8d62c1fbe8
22 changed files with 1025 additions and 35 deletions
54
rtut/run.py
54
rtut/run.py
|
|
@ -389,17 +389,67 @@ def effective_outcome(run: Run, now: datetime | None = None, stale_days: int = S
|
|||
return "abandoned" if (now - last).days >= stale_days else "stalled"
|
||||
|
||||
|
||||
def _ts(s: str) -> datetime:
|
||||
return datetime.fromisoformat(s)
|
||||
|
||||
|
||||
def fmt_duration(seconds: float | None) -> str:
|
||||
"""Human duration: '45 s', '3 min 20 s', '1 h 5 min', '2 d 3 h'."""
|
||||
if seconds is None:
|
||||
return "-"
|
||||
s = int(round(seconds))
|
||||
if s < 60:
|
||||
return f"{s} s"
|
||||
if s < 3600:
|
||||
return f"{s // 60} min {s % 60} s" if s % 60 else f"{s // 60} min"
|
||||
if s < 86400:
|
||||
return f"{s // 3600} h {(s % 3600) // 60} min" if (s % 3600) // 60 else f"{s // 3600} h"
|
||||
return f"{s // 86400} d {(s % 86400) // 3600} h" if (s % 86400) // 3600 else f"{s // 86400} d"
|
||||
|
||||
|
||||
def run_times(run: Run, now: datetime | None = None) -> dict:
|
||||
"""Start, finish and durations of one run.
|
||||
|
||||
elapsed: start to finish (to now while unfinished); working: the time steps were actually in progress;
|
||||
step_seconds: per step, from start to finish.
|
||||
"""
|
||||
now = now or datetime.now(timezone.utc)
|
||||
start = _ts(run.data["created"])
|
||||
fin = run.data.get("finished")
|
||||
end = _ts(fin) if fin else now
|
||||
steps = {}
|
||||
for sid, st in run.data["steps"].items():
|
||||
if st.get("started") and st.get("finished"):
|
||||
steps[sid] = max(0.0, (_ts(st["finished"]) - _ts(st["started"])).total_seconds())
|
||||
return {"started": run.data["created"], "finished": fin, "elapsed": max(0.0, (end - start).total_seconds()),
|
||||
"finished_run": bool(fin), "working": sum(steps.values()), "step_seconds": steps}
|
||||
|
||||
|
||||
def scorecard(pack: Pack, now: datetime | None = None, stale_days: int = STALE_DAYS) -> dict:
|
||||
"""How often a runbook was tried and how it went, derived from its run records."""
|
||||
"""How often a runbook was tried and how it went, derived from its run records.
|
||||
|
||||
avg_seconds / median_seconds / fastest_seconds are the elapsed times of *succeeded* runs only:
|
||||
unfinished and failed runs say nothing about how long a working run takes.
|
||||
"""
|
||||
card = {"tried": 0, "succeeded": 0, "failed": 0, "abandoned": 0, "stalled": 0,
|
||||
"feedback": {"good": 0, "neutral": 0, "poor": 0}}
|
||||
durations: list[float] = []
|
||||
for rid in list_runs(pack):
|
||||
r = load(pack, rid)
|
||||
card["tried"] += 1
|
||||
card[effective_outcome(r, now, stale_days)] += 1
|
||||
outcome = effective_outcome(r, now, stale_days)
|
||||
card[outcome] += 1
|
||||
if outcome == "succeeded" and r.data.get("finished"):
|
||||
durations.append(run_times(r)["elapsed"])
|
||||
fb = latest_feedback(r)
|
||||
if fb:
|
||||
card["feedback"][fb["rating"]] += 1
|
||||
durations.sort()
|
||||
card["avg_seconds"] = sum(durations) / len(durations) if durations else None
|
||||
card["median_seconds"] = (durations[len(durations) // 2] if len(durations) % 2 else
|
||||
(durations[len(durations) // 2 - 1] + durations[len(durations) // 2]) / 2) if durations else None
|
||||
card["fastest_seconds"] = durations[0] if durations else None
|
||||
card["timed_runs"] = len(durations)
|
||||
return card
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue