From 7e815920ac060cd2ea8237b8e3edbbaed9c6bf32 Mon Sep 17 00:00:00 2001 From: KaifAhmad1 Date: Sun, 19 Apr 2026 17:30:03 +0530 Subject: [PATCH] fix(provenance): resolve merge conflicts, fix session API, move schemas - Resolve all merge conflict markers in provenance.py, app.py, .gitignore - Revert broken session.get_nodes()/get_edges() to session.graph.nodes/edges - Keep undirected=True ego_graph fix for upstream ancestor traversal - Add direction field to ProvenanceEdge (upstream/downstream/lateral) - Group lineage edges in _render_markdown by direction section - Move ProvenanceNode/ProvenanceEdge/ProvenanceResponse to schemas.py - Restore complete router import set in app.py (sparql, vocabulary, etc.) Co-authored-by: Sameer6305 Co-authored-by: KaifAhmad1 --- .gitignore | 4 +- semantica/explorer/app.py | 9 ---- semantica/explorer/routes/provenance.py | 69 +++---------------------- semantica/explorer/schemas.py | 20 +++++++ 4 files changed, 29 insertions(+), 73 deletions(-) diff --git a/.gitignore b/.gitignore index f695e54f..c53c2817 100644 --- a/.gitignore +++ b/.gitignore @@ -111,11 +111,9 @@ sample_data/ # Test Results test_results.txt -<<<<<<< HEAD # Frontend workspace artifacts semantica-explorer/ node_modules/ -======= + # Frontend build artifacts (generated by Vite — do not track in git) semantica/static/ ->>>>>>> upstream/main diff --git a/semantica/explorer/app.py b/semantica/explorer/app.py index e4648314..852db430 100644 --- a/semantica/explorer/app.py +++ b/semantica/explorer/app.py @@ -92,16 +92,11 @@ def create_app(session: Optional[GraphSession] = None) -> FastAPI: from .routes.decisions import router as decisions_router from .routes.enrich import router as enrich_router from .routes.export_import import router as export_import_router -<<<<<<< HEAD - from .routes.annotations import router as annotations_router - from .routes.provenance import router as provenance_router -======= from .routes.graph import router as graph_router from .routes.provenance import router as provenance_router from .routes.sparql import router as sparql_router from .routes.temporal import router as temporal_router from .routes.vocabulary import router as vocabulary_router ->>>>>>> upstream/main app.include_router(graph_router) app.include_router(analytics_router) @@ -110,13 +105,9 @@ def create_app(session: Optional[GraphSession] = None) -> FastAPI: app.include_router(enrich_router) app.include_router(export_import_router) app.include_router(annotations_router) -<<<<<<< HEAD - app.include_router(provenance_router) -======= app.include_router(sparql_router) app.include_router(provenance_router) app.include_router(vocabulary_router) ->>>>>>> upstream/main _WS_MAX_MESSAGE_BYTES = 64 * 1024 # 64 KB — control messages only diff --git a/semantica/explorer/routes/provenance.py b/semantica/explorer/routes/provenance.py index c9ea52d6..8b1b1f03 100644 --- a/semantica/explorer/routes/provenance.py +++ b/semantica/explorer/routes/provenance.py @@ -1,8 +1,4 @@ -<<<<<<< HEAD """ -======= -""" ->>>>>>> upstream/main Provenance routes for lineage visualization and exportable reports. """ @@ -13,37 +9,13 @@ from typing import Any, Dict, List, Optional import networkx as nx from fastapi import APIRouter, Depends, Query from fastapi.responses import PlainTextResponse, Response -from pydantic import BaseModel from ..dependencies import get_session +from ..schemas import ProvenanceEdge, ProvenanceNode, ProvenanceResponse from ..session import GraphSession router = APIRouter(prefix="/api/provenance", tags=["Power User Tools"]) - -class ProvenanceNode(BaseModel): - id: str - label: str - prov_type: str - parent_id: str - - -class ProvenanceEdge(BaseModel): - id: str - source: str - target: str - label: str -<<<<<<< HEAD - direction: str # "upstream" | "downstream" | "lateral" -======= ->>>>>>> upstream/main - - -class ProvenanceResponse(BaseModel): - nodes: List[ProvenanceNode] - edges: List[ProvenanceEdge] - - _AGENT_TYPES = {"person", "organization", "system", "agent"} _ACTIVITY_TYPES = {"action", "event", "process", "activity", "decision", "publication"} @@ -61,32 +33,24 @@ def _build_provenance(session: GraphSession, node_id: Optional[str] = None) -> d if not node_id or node_id not in session.graph.nodes: return {"nodes": [], "edges": []} - graph = nx.DiGraph() - nodes = session.get_nodes() - edges = session.get_edges() - nodes_by_id = {node.id: node for node in nodes} - - if not node_id or node_id not in nodes_by_id: - return {"nodes": [], "edges": []} - graph = nx.DiGraph() graph.add_node(node_id) hop_nodes = {node_id} - for edge in edges: + for edge in session.graph.edges: if edge.source_id == node_id or edge.target_id == node_id: graph.add_edge(edge.source_id, edge.target_id, label=edge.edge_type) hop_nodes.add(edge.source_id) hop_nodes.add(edge.target_id) - for edge in edges: + for edge in session.graph.edges: if edge.source_id in hop_nodes or edge.target_id in hop_nodes: graph.add_edge(edge.source_id, edge.target_id, label=edge.edge_type) subgraph = nx.ego_graph(graph, node_id, radius=2, undirected=True) provenance_nodes: List[Dict[str, Any]] = [] for graph_node_id in subgraph.nodes(): - node = nodes_by_id.get(graph_node_id) + node = session.graph.nodes.get(graph_node_id) if node is None: continue prov_type, parent_id = _classify_prov(node.node_type) @@ -101,26 +65,19 @@ def _build_provenance(session: GraphSession, node_id: Optional[str] = None) -> d provenance_edges: List[Dict[str, Any]] = [] for source, target, data in subgraph.edges(data=True): -<<<<<<< HEAD if target == node_id: direction = "upstream" elif source == node_id: direction = "downstream" else: direction = "lateral" - -======= ->>>>>>> upstream/main provenance_edges.append( { "id": f"{source}-{target}", "source": source, "target": target, "label": data.get("label", "related_to"), -<<<<<<< HEAD "direction": direction, -======= ->>>>>>> upstream/main } ) @@ -159,39 +116,29 @@ def _render_markdown(report: Dict[str, Any]) -> str: for node in report.get("lineage", {}).get("nodes", []): lines.append(f"- `{node['id']}` ({node['prov_type']}): {node['label']}") -<<<<<<< HEAD edges = report.get("lineage", {}).get("edges", []) - grouped_edges = { - "upstream": [], - "downstream": [], - "lateral": [], - } + grouped_edges: Dict[str, List] = {"upstream": [], "downstream": [], "lateral": []} for edge in edges: direction = edge.get("direction", "lateral") if direction not in grouped_edges: direction = "lateral" grouped_edges[direction].append(edge) - if grouped_edges.get("upstream"): + if grouped_edges["upstream"]: lines.extend(["", "## Upstream"]) for edge in grouped_edges["upstream"]: lines.append(f"- `{edge['source']}` -[{edge['label']}]-> `{edge['target']}`") - if grouped_edges.get("downstream"): + if grouped_edges["downstream"]: lines.extend(["", "## Downstream"]) for edge in grouped_edges["downstream"]: lines.append(f"- `{edge['source']}` -[{edge['label']}]-> `{edge['target']}`") - if grouped_edges.get("lateral"): + if grouped_edges["lateral"]: lines.extend(["", "## Lateral"]) for edge in grouped_edges["lateral"]: lines.append(f"- `{edge['source']}` -[{edge['label']}]-> `{edge['target']}`") -======= - lines.extend(["", "## Lineage Edges"]) - for edge in report.get("lineage", {}).get("edges", []): - lines.append(f"- `{edge['source']}` -[{edge['label']}]-> `{edge['target']}`") ->>>>>>> upstream/main return "\n".join(lines) diff --git a/semantica/explorer/schemas.py b/semantica/explorer/schemas.py index c77badb1..46592de8 100644 --- a/semantica/explorer/schemas.py +++ b/semantica/explorer/schemas.py @@ -288,3 +288,23 @@ class MergeResponse(BaseModel): merged_into: str removed_ids: List[str] edges_updated: int + + +class ProvenanceNode(BaseModel): + id: str + label: str + prov_type: str + parent_id: Optional[str] = None + + +class ProvenanceEdge(BaseModel): + id: str + source: str + target: str + label: str + direction: str + + +class ProvenanceResponse(BaseModel): + nodes: List[ProvenanceNode] + edges: List[ProvenanceEdge]