Spaces:
Starting on CPU Upgrade

mgbam commited on
Commit
7808af5
Β·
verified Β·
1 Parent(s): d5e0cb0

Update mcp/graph_metrics.py

Browse files
Files changed (1) hide show
  1. mcp/graph_metrics.py +65 -31
mcp/graph_metrics.py CHANGED
@@ -1,63 +1,97 @@
1
  # mcp/graph_metrics.py
2
  """
3
- Tiny NetworkX helpers with forgiving edge parsing.
4
-
5
- Why this exists
6
- ---------------
7
- Your Streamlit-agraph edges carry keys ``source/target`` while other
8
- toolchains (PyVis, cytoscape, etc.) use ``from/to``. A naΓ―ve
9
- ``e["target"]`` lookup therefore raises **KeyError**. The functions below
10
- accept either spelling and silently skip malformed edges.
 
 
 
11
  """
12
 
13
  from __future__ import annotations
14
- from typing import List, Dict, Tuple
15
 
16
  import networkx as nx
 
17
 
18
 
19
- # ── private -----------------------------------------------------------------
20
- def _endpoints(edge: Dict) -> Tuple[str, str] | None:
21
- """Return (src,dst) tuple or *None* if either end is missing."""
22
- src = edge.get("source") or edge.get("from")
23
- dst = edge.get("target") or edge.get("to")
24
- return (src, dst) if src and dst else None
25
 
 
 
 
 
 
 
 
 
 
26
 
27
- # ── public helpers ----------------------------------------------------------
28
- def build_nx(nodes: List[Dict], edges: List[Dict]) -> nx.Graph:
 
 
29
  """
30
- Convert generic node/edge dictionaries into a NetworkX ``Graph``.
 
 
 
31
 
32
- β€’ Nodes are added with an attribute ``label`` (fallback: id).
33
- β€’ Edges are added only when both src & dst exist.
34
- β€’ **No KeyError** even if edge-schema is mixed.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
  """
36
  G = nx.Graph()
37
 
38
- # nodes
39
  for n in nodes:
40
- try:
41
- G.add_node(n["id"], label=n.get("label", n["id"]))
42
- except KeyError:
43
- # ignore malformed node dicts
44
  continue
 
45
 
46
- # edges
47
  for e in edges:
48
- ends = _endpoints(e)
 
 
49
  if ends:
50
  G.add_edge(*ends)
51
 
52
  return G
53
 
54
 
55
- def get_top_hubs(G: nx.Graph, k: int = 5):
56
- """Return *k* nodes with highest degree-centrality."""
 
 
57
  dc = nx.degree_centrality(G)
58
  return sorted(dc.items(), key=lambda x: x[1], reverse=True)[:k]
59
 
60
 
61
  def get_density(G: nx.Graph) -> float:
62
- """Graph density ∈ [0,1]."""
 
 
63
  return nx.density(G)
 
1
  # mcp/graph_metrics.py
2
  """
3
+ Tiny NetworkX helpers for MedGenesis graphs.
4
+
5
+ ✨ 2025-06-25 REVAMP
6
+ ────────────────────
7
+ β€’ Accepts edge-dicts that use either
8
+ {'source': 'n1', 'target': 'n2'} ← agraph / d3.js
9
+ or {'from' : 'n1', 'to' : 'n2'} ← PyVis
10
+
11
+ β€’ Silently skips malformed edges (no more KeyError).
12
+
13
+ β€’ Works whether you pass plain dicts or streamlit-agraph Node/Edge objects.
14
  """
15
 
16
  from __future__ import annotations
 
17
 
18
  import networkx as nx
19
+ from typing import List, Dict, Tuple, Union
20
 
21
 
22
+ # ── helpers -----------------------------------------------------------------
23
+ def _edge_ends(e: Dict) -> Tuple[str, str] | None:
24
+ """
25
+ Normalise edge formats.
 
 
26
 
27
+ Returns
28
+ -------
29
+ (src, dst) tuple – or None if either endpoint is missing.
30
+ """
31
+ src = e.get("source") or e.get("from")
32
+ dst = e.get("target") or e.get("to")
33
+ if src and dst:
34
+ return str(src), str(dst)
35
+ return None
36
 
37
+
38
+ def _node_id(n: Union[Dict, object]) -> str:
39
+ """
40
+ Accept either a dict *or* a streamlit_agraph.Node and return its id.
41
  """
42
+ if isinstance(n, dict):
43
+ return str(n.get("id"))
44
+ # fallback for Node dataclass
45
+ return str(getattr(n, "id", ""))
46
 
47
+
48
+ def _node_label(n: Union[Dict, object]) -> str:
49
+ """
50
+ Extract label safely from dict or Node.
51
+ """
52
+ if isinstance(n, dict):
53
+ return str(n.get("label", n.get("id")))
54
+ return str(getattr(n, "label", getattr(n, "id", "")))
55
+
56
+
57
+ # ── public API --------------------------------------------------------------
58
+ def build_nx(
59
+ nodes: List[Dict | object],
60
+ edges: List[Dict | object],
61
+ ) -> nx.Graph:
62
+ """
63
+ Convert generic node/edge payloads into a NetworkX graph.
64
  """
65
  G = nx.Graph()
66
 
67
+ # add nodes
68
  for n in nodes:
69
+ nid = _node_id(n)
70
+ if not nid:
 
 
71
  continue
72
+ G.add_node(nid, label=_node_label(n))
73
 
74
+ # add edges
75
  for e in edges:
76
+ if not isinstance(e, dict):
77
+ e = e.__dict__ # Edge dataclass β†’ dict
78
+ ends = _edge_ends(e)
79
  if ends:
80
  G.add_edge(*ends)
81
 
82
  return G
83
 
84
 
85
+ def get_top_hubs(G: nx.Graph, k: int = 5) -> List[Tuple[str, float]]:
86
+ """
87
+ Return top-k nodes by **degree centrality**.
88
+ """
89
  dc = nx.degree_centrality(G)
90
  return sorted(dc.items(), key=lambda x: x[1], reverse=True)[:k]
91
 
92
 
93
  def get_density(G: nx.Graph) -> float:
94
+ """
95
+ Graph density in [0, 1].
96
+ """
97
  return nx.density(G)