"""Project State Hub workplans/tasks into the graph-explorer payload. This is a *view* of State Hub coordination state, not Fabric topology. State Hub remains the authoring surface for workplans; Fabric only renders. """ from __future__ import annotations import json import os import re import urllib.request from datetime import datetime, timezone from typing import Any from urllib.parse import urlencode OPEN_WORKPLAN = frozenset({"proposed", "ready", "active", "blocked", "backlog"}) OPEN_TASK = frozenset({"todo", "progress", "wait"}) WP_ID_RE = re.compile(r"\b([A-Z]{2,12}(?:-[A-Z]+)?-WP-[A-Z0-9-]+)\b") def coordination_graph_payload( workplans: list[dict[str, Any]], tasks: list[dict[str, Any]], ) -> dict[str, Any]: """Build a GraphExplorerPayload of open workplans and their open tasks.""" open_wps = [wp for wp in workplans if str(wp.get("status") or "") in OPEN_WORKPLAN] wp_by_id = {str(wp.get("id")): wp for wp in open_wps if wp.get("id")} slug_to_id = { str(wp.get("slug") or "").lower(): str(wp["id"]) for wp in open_wps if wp.get("slug") and wp.get("id") } # also index record ids like CUST-WP-0071 from title/slug record_to_id: dict[str, str] = {} for wp in open_wps: slug = str(wp.get("slug") or "") title = str(wp.get("title") or "") for token in WP_ID_RE.findall(slug.upper() + " " + title.upper()): record_to_id[token] = str(wp["id"]) if slug: record_to_id[slug.upper().replace("_", "-")] = str(wp["id"]) open_tasks = [ task for task in tasks if str(task.get("status") or "") in OPEN_TASK and str(task.get("workplan_id") or task.get("workstream_id") or "") in wp_by_id ] elements: list[dict[str, Any]] = [] for wp in open_wps: wp_id = str(wp["id"]) status = str(wp.get("status") or "unknown") elements.append( { "data": { "id": f"workplan:{wp_id}", "stableKey": f"workplan:{wp_id}", "kind": "Workplan", "layer": "workplan", "label": str(wp.get("slug") or wp.get("title") or wp_id)[:48], "name": wp.get("title"), "lifecycle": status, "status": status, "repo": wp.get("repo") or wp.get("slug"), "displayState": "show" if status != "blocked" else "highlight", "unresolved": status in {"blocked", "proposed"}, "chokepoint": 0, } } ) belongs = 0 wait_edges = 0 for task in open_tasks: task_id = str(task["id"]) wp_id = str(task.get("workplan_id") or task.get("workstream_id")) status = str(task.get("status") or "todo") human = bool(task.get("needs_human")) elements.append( { "data": { "id": f"task:{task_id}", "stableKey": f"task:{task_id}", "kind": "Task", "layer": "task", "label": str(task.get("title") or task_id)[:56], "name": task.get("title"), "lifecycle": status, "status": status, "needsHuman": human, "displayState": "highlight" if status == "wait" or human else "show", "unresolved": status == "wait" or human, } } ) elements.append( { "data": { "id": f"edge:belongs:{task_id}", "stableKey": f"edge:belongs:{task_id}", "kind": "Edge", "layer": "dependency", "displayState": "show", "source": f"task:{task_id}", "target": f"workplan:{wp_id}", "edgeType": "belongs_to", "strength": "weak", "sourceLayer": "task", "targetLayer": "workplan", } } ) belongs += 1 if status == "wait" or human: wait_edges += 1 # Cross-workplan citations in wait/blocking text. cited = 0 for task in open_tasks: blob = " ".join( str(task.get(key) or "") for key in ("description", "blocking_reason", "intervention_note", "title") ) src_wp = str(task.get("workplan_id") or task.get("workstream_id")) for token in set(WP_ID_RE.findall(blob.upper())): dst = record_to_id.get(token) or slug_to_id.get(token.lower()) if dst and dst != src_wp and dst in wp_by_id: elements.append( { "data": { "id": f"edge:cites:{task['id']}:{dst}", "stableKey": f"edge:cites:{task['id']}:{dst}", "kind": "Edge", "layer": "dependency", "displayState": "show", "source": f"workplan:{src_wp}", "target": f"workplan:{dst}", "edgeType": "waits_on", "strength": "strong", "sourceLayer": "workplan", "targetLayer": "workplan", } } ) cited += 1 for el in elements: if el["data"]["id"] == f"workplan:{dst}": el["data"]["chokepoint"] = int(el["data"].get("chokepoint") or 0) + 1 generated = datetime.now(timezone.utc).isoformat() return { "apiVersion": "railiance.fabric/v1alpha1", "kind": "GraphExplorerPayload", "manifest_id": "railiance-fabric.coordination-map", "generated_at": generated, "mode": "coordination", "metrics": { "open_workplans": len(open_wps), "open_tasks": len(open_tasks), "belongs_to_edges": belongs, "citation_edges": cited, "wait_or_human_tasks": wait_edges, }, "elements": elements, "hidden_elements": [], } def fetch_hub_lists(api_base: str) -> tuple[list[dict[str, Any]], list[dict[str, Any]]]: base = api_base.rstrip("/") workplans: list[dict[str, Any]] = [] for status in sorted(OPEN_WORKPLAN): workplans.extend(_get_json(f"{base}/workplans/?{urlencode({'status': status})}")) tasks = _get_json(f"{base}/tasks/") return workplans, tasks def _get_json(url: str) -> list[dict[str, Any]]: with urllib.request.urlopen(url, timeout=60) as response: payload = json.loads(response.read().decode()) if isinstance(payload, list): return payload raise RuntimeError(f"unexpected JSON from {url}") def default_hub() -> str: return os.environ.get("STATE_HUB_URL") or os.environ.get("API_BASE") or "http://127.0.0.1:8000"