Find nodes with highest betweenness centrality. These are architectural chokepoints that sit on shortest paths between many node pairs. If they break, multiple communities lose connectivity. Returns list of dicts with: name, qualified_name, kind, file, betweenness, community_id
(
store: GraphStore, top_n: int = 10
)
| 56 | |
| 57 | |
| 58 | def find_bridge_nodes( |
| 59 | store: GraphStore, top_n: int = 10 |
| 60 | ) -> list[dict]: |
| 61 | """Find nodes with highest betweenness centrality. |
| 62 | |
| 63 | These are architectural chokepoints that sit on shortest paths |
| 64 | between many node pairs. If they break, multiple communities |
| 65 | lose connectivity. |
| 66 | |
| 67 | Returns list of dicts with: name, qualified_name, kind, file, |
| 68 | betweenness, community_id |
| 69 | """ |
| 70 | import networkx as nx |
| 71 | |
| 72 | # Build the graph — use cached version if available |
| 73 | nxg = store._build_networkx_graph() |
| 74 | |
| 75 | # Compute betweenness centrality (approximate for large graphs) |
| 76 | n_nodes = nxg.number_of_nodes() |
| 77 | if n_nodes > 5000: |
| 78 | # Sample-based approximation for large graphs |
| 79 | k = min(500, n_nodes) |
| 80 | bc = nx.betweenness_centrality(nxg, k=k, normalized=True) |
| 81 | elif n_nodes > 0: |
| 82 | bc = nx.betweenness_centrality(nxg, normalized=True) |
| 83 | else: |
| 84 | return [] |
| 85 | |
| 86 | community_map = store.get_all_community_ids() |
| 87 | node_map = { |
| 88 | n.qualified_name: n |
| 89 | for n in store.get_all_nodes(exclude_files=True) |
| 90 | } |
| 91 | |
| 92 | results = [] |
| 93 | for qn, score in bc.items(): |
| 94 | if score <= 0 or qn not in node_map: |
| 95 | continue |
| 96 | n = node_map[qn] |
| 97 | if n.kind == "File": |
| 98 | continue |
| 99 | results.append({ |
| 100 | "name": _sanitize_name(n.name), |
| 101 | "qualified_name": n.qualified_name, |
| 102 | "kind": n.kind, |
| 103 | "file": n.file_path, |
| 104 | "betweenness": round(score, 6), |
| 105 | "community_id": community_map.get(qn), |
| 106 | }) |
| 107 | |
| 108 | results.sort( |
| 109 | key=lambda x: float(x.get("betweenness", 0)), # type: ignore[arg-type,return-value] |
| 110 | reverse=True, |
| 111 | ) |
| 112 | return results[:top_n] |
| 113 | |
| 114 | |
| 115 | def find_knowledge_gaps(store: GraphStore) -> dict[str, list[dict]]: |
no test coverage detected