"""core/causal_graph.py — Graphe causal NetworkX""" import networkx as nx, hashlib, time, sqlite3, os from typing import List, Dict, Optional from pathlib import Path DATA_DIR = Path(os.environ.get("DATA_DIR", "/tmp/vortex_data")) class CausalGraph: def __init__(self): self.graph = nx.MultiDiGraph() self._db = None def _get_db(self): if self._db is None: DATA_DIR.mkdir(parents=True, exist_ok=True) self._db = sqlite3.connect(str(DATA_DIR / "causal.db"), check_same_thread=False) self._db.execute( "CREATE TABLE IF NOT EXISTS causal_edges " "(id TEXT PRIMARY KEY, src TEXT, tgt TEXT, relation TEXT, weight REAL, ts REAL)" ) return self._db def add_node(self, nid: str, kind: str, attrs: Optional[Dict] = None): self.graph.add_node(nid, kind=kind, **(attrs or {})) def add_edge(self, src: str, tgt: str, relation: str = "causes", weight: float = 1.0): eid = hashlib.md5(f"{src}{tgt}{relation}".encode()).hexdigest()[:12] for n in (src, tgt): if n not in self.graph: self.graph.add_node(n, kind="unknown") self.graph.add_edge(src, tgt, relation=relation, weight=weight) db = self._get_db() db.execute( "INSERT OR REPLACE INTO causal_edges VALUES (?,?,?,?,?,?)", (eid, src, tgt, relation, weight, time.time()) ) db.commit() return True def causal_query(self, src: str, tgt: Optional[str] = None, max_depth: int = 3) -> List[Dict]: if src not in self.graph: return [] try: if tgt and tgt in self.graph: paths = list(nx.all_simple_paths(self.graph, src, tgt, cutoff=max_depth)) return [{"path": p, "length": len(p)} for p in paths[:10]] result = [] for node in nx.bfs_tree(self.graph, src, depth_limit=max_depth).nodes(): if node != src: result.append({ "node": node, "depth": nx.shortest_path_length(self.graph, src, node) }) return result[:20] except Exception as e: return [{"error": str(e)}] def stats(self): return {"nodes": self.graph.number_of_nodes(), "edges": self.graph.number_of_edges()}