from __future__ import annotations from graph.models import GraphRun def graph_run_inspection(graph_run: GraphRun) -> dict[str, object]: spec = graph_run.execution_graph_version.graph_spec node_runs = { node.node_id: node for node in graph_run.node_runs.order_by("node_id", "-visit_index") } nodes = [] for node_id, node_spec in spec.get("nodes", {}).items(): run = node_runs.get(node_id) nodes.append( { "id": node_id, "type": node_spec.get("type"), "status": run.status if run else "PENDING", "visit_index": run.visit_index if run else 0, "duration_ms": (run.telemetry or {}).get("duration_ms") if run else None, "failure": run.failure_evidence if run else {}, "metadata": node_spec.get("metadata", {}), } ) return { "graph": graph_run.execution_graph_version.graph.name, "version": graph_run.execution_graph_version.version, "version_status": graph_run.execution_graph_version.status, "version_promoted_at": graph_run.execution_graph_version.promoted_at, "status": graph_run.status, "current_node": graph_run.current_node, "final_failure_reason": graph_run.metadata.get("final_failure_reason") or graph_run.failure_reason or None, "historical_failures": graph_run.metadata.get("historical_failures", []), "nodes": nodes, "edges": spec.get("edges", []), "edge_traversals": list( graph_run.edge_traversals.order_by("created_at").values("source_node", "target_node", "condition", "result", "metadata", "created_at") ), "failures": list(graph_run.node_runs.exclude(failure_evidence={}).values("node_id", "failure_evidence")), }