Files
semantica/examples/explorer_deterministic_rendering_example.py
T
Alex Smolya d3183d0ab3 feat(explorer): add deterministic rendering E2E example and test (#1037) (#1041)
feat(explorer): add deterministic rendering E2E example and test (#1037)

Adds a deterministic Explorer graph baseline and coverage for the full
build -> persist -> API -> frontend hydration -> canvas rendering path,
so a regression anywhere along that chain shows up in CI instead manually.

examples/explorer_deterministic_rendering_example.py builds the
canonical 4-node, 3-edge graph (Alice -WORKS_AT-> Acme, Bob -KNOWS->
Alice, Acme -LOCATED_IN-> New York) with ContextGraph.add_node()/
add_edge(), persists it with save_to_file() and reloads it with
GraphSession.from_file(), printing the setup prerequisites and the
expected node/edge/label checklist for anyone running it by hand.

tests/explorer/test_explorer_deterministic_rendering_e2e.py covers
graph construction, the serialize/deserialize round trip, GraphSession
loading, and the Explorer API's /api/graph/* responses against the
exact expected nodes, edges, and labels, plus all three auth modes
(unconfigured, API-key required, anonymous opt-in).

fix(explorer): address Qodo review findings for deterministic rendering e2e (#1037)

- configure SEMANTICA_ALLOW_ANONYMOUS=true and document
  SEMANTICA_API_KEY as the alternative in the reproduction
  instructions, so the documented commands don't 503 on a clean
  checkout
- add clean-checkout prerequisites and a visual verification
  checklist to the example
- add edge-label (WORKS_AT, KNOWS, LOCATED_IN), zoom-tier, and
  hover-interaction coverage to the frontend test
- add an explicit auth-enforcement integration test for the
  deterministic graph endpoints

fix(explorer): connect deterministic rendering E2E path

The frontend test built its own node/edge objects directly with
batchMergeNodes()/batchMergeEdges(), bypassing the real loading path
entirely -- it never went through useLoadGraph, never mounted the
canvas, and its fixture didn't even carry the same fields the backend
actually returns (e.g. no color values), so a break in API hydration,
the edge.type -> edgeType mapping, or canvas label rendering could
still pass.

Adds deterministicExplorerRendering.e2e.ts, which mounts the real
Explorer app in Chromium, serves API-shaped /api/graph/nodes and
/api/graph/edges responses through route interception, drives the
app through its actual useLoadGraph hydration path into a real Sigma
canvas, and asserts on captured canvas fillText() calls that
WORKS_AT, KNOWS, and LOCATED_IN are genuinely drawn, both after load
and after Zoom In.

fix(explorer): preserve upstream markdown dependencies
ci(explorer): isolate deterministic backend test dependencies

Wires the new Python test into ci.yml as its own focused step (it
previously only ran manually), installs Playwright's Chromium
browser before the frontend suite, and keeps the deterministic
backend test's dependency install separate from the rest of the
pipeline so it doesn't pull in unrelated optional extras during
collection.

fix(explorer): remove redundant edge label hydration

An earlier commit in this PR added an explicit `label` field to
hydrated edge attributes on the theory that it was needed for edge
labels to render. Review traced through GraphCanvas.tsx's label
resolution (`attrs.edgeType || data.label || ""`, from the earlier
#1009 fix already on main) and found that `edgeType` is set
unconditionally on every edge during hydration, so it always wins the
`||` before `data.label` is ever consulted -- the added field and its
plumbing in useLoadGraph.ts and graphStore.ts never did anything.
Removed both; reran the real Chromium E2E test against the reverted
code and confirmed all three labels still render identically, closing
out the question of whether anything else was actually broken.
2026-08-29 12:25:58 +05:00

196 lines
6.9 KiB
Python

"""
Deterministic Explorer Rendering E2E Example.
Demonstrates building, serializing, and reloading a deterministic 4-node,
3-edge knowledge graph baseline for visual inspection in Semantica Explorer (#1037).
Graph topology:
Alice (Person, #63E6FF) --WORKS_AT--> Acme (Organization, #A78BFA)
Bob (Person, #63E6FF) --KNOWS--> Alice (Person, #63E6FF)
Acme (Organization, #A78BFA) --LOCATED_IN--> New York (Location, #34D399)
Clean Checkout Prerequisites:
1. Python backend dependencies:
pip install -e ".[explorer]"
2. Frontend workspace dependencies:
cd explorer && npm install && cd ..
Usage:
# 1. Generate the deterministic graph baseline:
python examples/explorer_deterministic_rendering_example.py
# 2. Launch Explorer with local dev authentication (Option A - Dev mode):
# Terminal 1 (Backend API):
SEMANTICA_ALLOW_ANONYMOUS=true python -m semantica.explorer --graph explorer_e2e_test_graph.json --port 8000 --no-browser
# Terminal 2 (Frontend UI):
cd explorer && npm run dev
# Open http://localhost:5173
# 2. Launch Explorer (Option B - Standalone CLI server):
SEMANTICA_ALLOW_ANONYMOUS=true python -m semantica.explorer --graph explorer_e2e_test_graph.json --port 8000
# Open http://localhost:8000
# Secure authentication alternative:
export SEMANTICA_API_KEY="your-secret-api-key"
python -m semantica.explorer --graph explorer_e2e_test_graph.json --port 8000
# Send HTTP header: X-API-Key: your-secret-api-key
Verification Checklist:
- Exactly 4 nodes visible on canvas:
* Alice (Person, #63E6FF)
* Bob (Person, #63E6FF)
* Acme (Organization, #A78BFA)
* New York (Location, #34D399)
- Exactly 3 directed edges with canonical relationship labels:
* Alice -> Acme (WORKS_AT)
* Bob -> Alice (KNOWS)
* Acme -> New York (LOCATED_IN)
- Zoom behavior:
* Zoom in to Inspection tier (ratio <= 0.5): directional arrows and node labels scale clearly.
* Zoom out to Overview tier (ratio > 1.2): layout remains stable and non-colliding.
- Hover & Selection interactions:
* Hover over 'Alice': node halo triggers; incident edges (WORKS_AT, KNOWS) highlight in local context.
* Click an edge: Inspector panel confirms edgeType ('WORKS_AT', 'KNOWS', or 'LOCATED_IN').
"""
from __future__ import annotations
import json
from pathlib import Path
from semantica.context.context_graph import ContextGraph
from semantica.explorer.session import GraphSession
def build_deterministic_graph() -> ContextGraph:
"""Build the exact 4-node, 3-edge graph specified in #1037."""
graph = ContextGraph(advanced_analytics=False)
# 1. Add exactly 4 nodes
graph.add_node(
"alice",
node_type="Person",
content="Alice",
color="#63E6FF",
)
graph.add_node(
"bob",
node_type="Person",
content="Bob",
color="#63E6FF",
)
graph.add_node(
"acme",
node_type="Organization",
content="Acme",
color="#A78BFA",
)
graph.add_node(
"new_york",
node_type="Location",
content="New York",
color="#34D399",
)
# 2. Add exactly 3 directed edges
graph.add_edge("alice", "acme", edge_type="WORKS_AT", weight=1.0)
graph.add_edge("bob", "alice", edge_type="KNOWS", weight=1.0)
graph.add_edge("acme", "new_york", edge_type="LOCATED_IN", weight=1.0)
return graph
def main() -> None:
print("=" * 75)
print("Semantica Explorer Deterministic Graph Generator (#1037)")
print("=" * 75)
print("1. Building deterministic ContextGraph...")
graph = build_deterministic_graph()
print(
f" ✓ Graph built with {len(graph.nodes)} nodes "
f"and {len(graph.edges)} edges."
)
output_path = Path("explorer_e2e_test_graph.json").resolve()
print(f"2. Persisting graph to '{output_path.name}'...")
graph.save_to_file(str(output_path))
print(f" ✓ Graph saved to {output_path}")
# Verify JSON format
with open(output_path, "r", encoding="utf-8") as f:
data = json.load(f)
assert len(data.get("nodes", [])) == 4
assert len(data.get("edges", [])) == 3
print("3. Verifying reload via GraphSession.from_file()...")
session = GraphSession.from_file(str(output_path))
stats = session.get_stats()
nodes, total_nodes = session.get_nodes()
edges, total_edges = session.get_edges()
assert stats["node_count"] == 4
assert stats["edge_count"] == 3
assert total_nodes == 4
assert total_edges == 3
print(
f" ✓ Graph reloaded successfully without mutation "
f"(nodes: {total_nodes}, edges: {total_edges}).\n"
)
print("=" * 75)
print("Clean Checkout Prerequisites:")
print("=" * 75)
print(" pip install -e '.[explorer]'")
print(" cd explorer && npm install && cd ..\n")
print("=" * 75)
print("Reproduction instructions to view in Semantica Explorer:")
print("=" * 75)
print("Option A (Frontend dev server + API backend — recommended for development):")
print(
f" 1. Backend: SEMANTICA_ALLOW_ANONYMOUS=true python -m semantica.explorer "
f"--graph {output_path} --port 8000 --no-browser"
)
print(" 2. Frontend: cd explorer && npm run dev")
print(" 3. Open http://localhost:5173 to inspect the graph canvas.\n")
print("Option B (Standalone Explorer CLI server):")
print(
f" SEMANTICA_ALLOW_ANONYMOUS=true python -m semantica.explorer "
f"--graph {output_path} --port 8000"
)
print(" Open http://localhost:8000\n")
print("Secure Authentication Alternative:")
print(" export SEMANTICA_API_KEY='your-secret-api-key'")
print(
f" python -m semantica.explorer --graph {output_path} --port 8000"
)
print(" Send header: 'X-API-Key: your-secret-api-key'\n")
print("=" * 75)
print("Verification Checklist:")
print("=" * 75)
print(" 1. Nodes (4 total):")
print(" - Alice (Person, #63E6FF)")
print(" - Bob (Person, #63E6FF)")
print(" - Acme (Organization, #A78BFA)")
print(" - New York (Location, #34D399)")
print(" 2. Directed Edges & Canonical Labels (3 total):")
print(" - Alice -> Acme [WORKS_AT]")
print(" - Bob -> Alice [KNOWS]")
print(" - Acme -> New York [LOCATED_IN]")
print(" 3. Zoom Interactions:")
print(" - Inspection tier (zoom in): directional arrows & labels remain legible.")
print(" - Overview tier (zoom out): nodes and edges maintain layout integrity.")
print(" 4. Hover & Selection Interactions:")
print(" - Hover Alice: node halo triggers and incident edges (WORKS_AT, KNOWS) highlight.")
print(" - Click edge: Inspector panel displays edgeType label ('WORKS_AT', 'KNOWS', 'LOCATED_IN').")
print("=" * 75)
if __name__ == "__main__":
main()