"""Regenerate graphify-out/graph.html from the existing graph.json.
graphify's normal build writes both graph.json and the interactive
graph.html viewer, but only graph.json was committed here (the
Agent Time page links to /dashboard/graphify-out/graph.html, which 404s).
This reuses graphify's own exporters.html.to_html on the already
built graph.json so the viewer actually exists.
Run: . venv/bin/activate && python3 regen_graph_html.py
"""
from pathlib import Path
import json
import networkx as nx
from graphify.exporters.html import to_html
ROOT = Path(__file__).resolve().parent
GRAPH_JSON = ROOT / "graphify-out" / "graph.json"
OUT = ROOT / "graphify-out" / "graph.html"
data = json.loads(GRAPH_JSON.read_text())
nodes = data.get("nodes", [])
links = data.get("links", [])
G = nx.Graph()
for n in nodes:
nid = n.get("id") or n.get("name")
if nid is None:
continue
attrs = {k: v for k, v in n.items() if k not in ("id", "name")}
G.add_node(nid, **attrs)
for e in links:
s = e.get("source") or e.get("from")
t = e.get("target") or e.get("to")
if s is None or t is None:
continue
eattrs = {k: v for k, v in e.items() if k not in ("source", "target", "from", "to")}
G.add_edge(s, t, **eattrs)
print(f"graph: {G.number_of_nodes()} nodes, {G.number_of_edges()} edges")
# Real communities so the aggregated fallback (used when node count is high)
# has something valid to render instead of skipping.
try:
import networkx.algorithms.community as nxcomm
comm_list = nxcomm.greedy_modularity_communities(G)
communities = {i: list(c) for i, c in enumerate(comm_list)}
except Exception as ex:
print(f"community detection failed ({ex}); using single community")
communities = {0: list(G.nodes())}
to_html(G, communities, str(OUT), node_limit=4000)
print(f"wrote {OUT} ({OUT.stat().st_size} bytes)")