#!/usr/bin/env python3 """ aggregate.py — Time-to-Solution dashboard aggregator (stdlib only). Reads an append-only event log (JSON-lines, per SPEC.md) and writes metrics.json. No dependencies, no DB, no server. Run it against events.jsonl after any build: python aggregate.py events.sample.jsonl metrics.json """ import json, sys, collections def load(path): ev = [] for line in open(path, encoding="utf-8"): line = line.strip() if line: ev.append(json.loads(line)) return ev # site/documentation meta-work (building this very site) is not the engineering solution — exclude it EXCLUDE_DOMAINS = {"docs", "telemetry"} def aggregate(events): events = [e for e in events if e.get("domain") not in EXCLUDE_DOMAINS] work = [e for e in events if e["event"] == "work_completed"] touch = [e for e in events if e["event"] == "human_touch"] seams = [e for e in events if e["event"] == "seam_crossing"] rework = [e for e in events if e["event"] == "rework"] verif = [e for e in events if e["event"] == "verification"] cost = [e for e in events if e["event"] == "cost"] time_by_domain = collections.OrderedDict() iters_by_domain = {} for e in work: d = e["domain"] time_by_domain[d] = time_by_domain.get(d, 0) + int(e.get("duration_s", 0)) it = e.get("meta", {}).get("iterations") if it is not None: iters_by_domain[d] = iters_by_domain.get(d, 0) + int(it) # attribute seam time to its domain too for e in seams: d = e["domain"] time_by_domain[d] = time_by_domain.get(d, 0) + int(e.get("duration_s", 0)) work_s = sum(int(e.get("duration_s", 0)) for e in work) seam_s = sum(int(e.get("duration_s", 0)) for e in seams) tts_s = work_s + seam_s by_kind = collections.Counter(e.get("meta", {}).get("kind", "?") for e in touch) human_s = sum(int(e.get("duration_s", 0)) for e in touch) seam_fail = sum(int(e.get("meta", {}).get("failures", 0)) for e in seams) mc = sum(1 for e in verif if e.get("meta", {}).get("machine_checkable")) coverage = round(100.0 * mc / len(verif), 1) if verif else 0.0 deliverables = sorted({e["deliverable"] for e in work}) ndel = len(deliverables) tokens = sum(int(e.get("meta", {}).get("tokens", 0)) for e in cost) lic_h = sum(int(e.get("meta", {}).get("license_hours", 0)) for e in cost) m = collections.OrderedDict() m["run_id"] = events[0].get("run_id") if events else None m["headline"] = { "tts_hours": round(tts_s / 3600.0, 1), # agent-active build time "human_touches": len(touch), "prompts_per_deliverable": round(by_kind.get("prompt", 0) / ndel, 1) if ndel else 0, "iterations_total": sum(iters_by_domain.values()), "rework_events": len(rework), "verification_coverage_pct": coverage, } m["time_by_domain_hours"] = collections.OrderedDict( (d, round(s / 3600.0, 2)) for d, s in sorted(time_by_domain.items(), key=lambda kv: -kv[1])) m["iterations_by_domain"] = dict(sorted(iters_by_domain.items(), key=lambda kv: -kv[1])) m["human"] = { "count": len(touch), "by_kind": dict(by_kind), "in_loop_minutes": round(human_s / 60.0, 1), # autonomy by TIME is high and misleading; the real signal is touch COUNT / kind: "note": "prompts + unblocks are the touches the agentic program layer must absorb; only 'approve' should remain", } m["rework"] = { "count": len(rework), "escape_rate_per_deliverable": round(len(rework) / ndel, 2) if ndel else 0, "items": [{"deliverable": e["deliverable"], "cause": e["meta"].get("cause"), "upstream": e["meta"].get("upstream")} for e in rework], } m["seams"] = { "count": len(seams), "total_minutes": round(seam_s / 60.0, 1), "tax_pct_of_tts": round(100.0 * seam_s / tts_s, 1) if tts_s else 0, "failures": seam_fail, "items": [{"from": e["meta"].get("from"), "to": e["meta"].get("to"), "minutes": round(int(e.get("duration_s", 0)) / 60.0, 1), "failures": int(e["meta"].get("failures", 0)), "note": e["meta"].get("note")} for e in seams], } m["verification"] = {"total": len(verif), "machine_checkable": mc, "coverage_pct": coverage} m["deliverables"] = {"count": ndel, "list": deliverables} m["cost"] = {"tokens": tokens, "license_hours": lic_h} return m if __name__ == "__main__": src = sys.argv[1] if len(sys.argv) > 1 else "events.sample.jsonl" dst = sys.argv[2] if len(sys.argv) > 2 else "metrics.json" metrics = aggregate(load(src)) json.dump(metrics, open(dst, "w", encoding="utf-8"), indent=2) print(json.dumps(metrics, indent=2))