diff --git a/nexus/mnemosyne/archive.py b/nexus/mnemosyne/archive.py index 8a8a2eb8..7481ad52 100644 --- a/nexus/mnemosyne/archive.py +++ b/nexus/mnemosyne/archive.py @@ -212,6 +212,65 @@ class MnemosyneArchive: def count(self) -> int: return len(self._entries) + def graph_data( + self, + topic_filter: Optional[str] = None, + ) -> dict: + """Export the full connection graph for 3D constellation visualization. + + Returns a dict with: + - nodes: list of {id, title, topics, source, created_at} + - edges: list of {source, target, weight} from holographic links + + Args: + topic_filter: If set, only include entries matching this topic + and edges between them. + """ + entries = list(self._entries.values()) + + if topic_filter: + topic_lower = topic_filter.lower() + entries = [ + e for e in entries + if topic_lower in [t.lower() for t in e.topics] + ] + + entry_ids = {e.id for e in entries} + + nodes = [ + { + "id": e.id, + "title": e.title, + "topics": e.topics, + "source": e.source, + "created_at": e.created_at, + } + for e in entries + ] + + # Build edges from links, dedup (A→B and B→A become one edge) + seen_edges: set[tuple[str, str]] = set() + edges = [] + for e in entries: + for linked_id in e.links: + if linked_id not in entry_ids: + continue + pair = (min(e.id, linked_id), max(e.id, linked_id)) + if pair in seen_edges: + continue + seen_edges.add(pair) + # Compute weight via linker for live similarity score + linked = self._entries.get(linked_id) + if linked: + weight = self.linker.compute_similarity(e, linked) + edges.append({ + "source": pair[0], + "target": pair[1], + "weight": round(weight, 4), + }) + + return {"nodes": nodes, "edges": edges} + def stats(self) -> dict: entries = list(self._entries.values()) total_links = sum(len(e.links) for e in entries)