diff --git a/graphify/analyze.py b/graphify/analyze.py index 995b5fb7..cf534496 100644 --- a/graphify/analyze.py +++ b/graphify/analyze.py @@ -3,6 +3,11 @@ from __future__ import annotations import networkx as nx +def _node_community_map(communities: dict[int, list[str]]) -> dict[str, int]: + """Invert communities dict: node_id -> community_id.""" + return {n: cid for cid, nodes in communities.items() for n in nodes} + + def _is_file_node(G: nx.Graph, node_id: str) -> bool: """ Return True if this node is a file-level hub node (e.g. 'client', 'models') @@ -187,7 +192,7 @@ def _cross_file_surprises(G: nx.Graph, communities: dict[int, list[str]], top_n: Each result includes a 'why' field explaining what makes it non-obvious. """ - node_community = {n: cid for cid, nodes in communities.items() for n in nodes} + node_community = _node_community_map(communities) candidates = [] for u, v, data in G.edges(data=True): @@ -266,7 +271,7 @@ def _cross_community_surprises( return result # Build node → community map - node_community = {n: cid for cid, nodes in communities.items() for n in nodes} + node_community = _node_community_map(communities) surprises = [] for u, v, data in G.edges(data=True): @@ -325,7 +330,7 @@ def suggest_questions( Each question has a 'type', 'question', and 'why' field. """ questions = [] - node_community = {n: cid for cid, nodes in communities.items() for n in nodes} + node_community = _node_community_map(communities) # 1. AMBIGUOUS edges → unresolved relationship questions for u, v, data in G.edges(data=True): diff --git a/graphify/cache.py b/graphify/cache.py index 99db860d..0bde3bb7 100644 --- a/graphify/cache.py +++ b/graphify/cache.py @@ -3,7 +3,6 @@ from __future__ import annotations import hashlib import json -import shutil from pathlib import Path diff --git a/graphify/detect.py b/graphify/detect.py index 7c623f3d..3c740688 100644 --- a/graphify/detect.py +++ b/graphify/detect.py @@ -1,6 +1,7 @@ # file discovery, type classification, and corpus health checks from __future__ import annotations import json +import os import re from enum import Enum from pathlib import Path @@ -155,7 +156,6 @@ def detect(root: Path) -> dict: for scan_root in scan_paths: in_memory_tree = memory_dir.exists() and str(scan_root).startswith(str(memory_dir)) - import os for dirpath, dirnames, filenames in os.walk(scan_root, followlinks=False): dp = Path(dirpath) if not in_memory_tree: diff --git a/graphify/export.py b/graphify/export.py index 9035f3dc..14306328 100644 --- a/graphify/export.py +++ b/graphify/export.py @@ -17,8 +17,186 @@ COMMUNITY_COLORS = [ MAX_NODES_FOR_VIZ = 5_000 +def _node_community_map(communities: dict[int, list[str]]) -> dict[str, int]: + """Invert communities dict: node_id -> community_id.""" + return {n: cid for cid, nodes in communities.items() for n in nodes} + + +def _html_styles() -> str: + return """""" + + +def _html_script(nodes_json: str, edges_json: str, legend_json: str) -> str: + return f"""""" + + def to_json(G: nx.Graph, communities: dict[int, list[str]], output_path: str) -> None: - node_community = {n: cid for cid, nodes in communities.items() for n in nodes} + node_community = _node_community_map(communities) data = json_graph.node_link_data(G, edges="links") for node in data["nodes"]: node["community"] = node_community.get(node["id"]) @@ -62,7 +240,7 @@ def to_html( f"Use --no-viz or reduce input size." ) - node_community = {n: cid for cid, nodes in communities.items() for n in nodes} + node_community = _node_community_map(communities) degree = dict(G.degree()) max_deg = max(degree.values()) if degree else 1 @@ -118,6 +296,7 @@ def to_html( edges_json = json.dumps(vis_edges) legend_json = json.dumps(legend_data) title = sanitize_label(str(output_path)) + stats = f"{G.number_of_nodes()} nodes · {G.number_of_edges()} edges · {len(communities)} communities" html = f""" @@ -125,36 +304,7 @@ def to_html(