Graph projection

Neo4j is optional. PostgreSQL and the API do not depend on it. The canonical YAML remains authoritative.

python -m scripts.export_graph

This writes artifacts/graph.json and a self-contained graph.cypher. The graph is deterministic for identical canonical content and dataset version. Export validation rejects missing edge endpoints, unknown predicates and duplicate graph IDs. Every node uses an opensensation: identifier and a fixed allowlisted label.

Labels include Modality, Stimulus, Sensor, Receptor, Transduction, Signal, ProcessingStep, Sensation, Threshold, Range, ContextModifier, Percept, Capability, Limitation, FailureMode, Source, Evidence, Contributor and ExpertReview. All have the OpenSensation label and namespace property. Edges retain claim ID, evidence status, conditions, source count, dataset version and source-supported association strength when supplied. Claim citations produce SUPPORTED_BY edges. Full validated record JSON remains on each node for lossless provenance.

MATCH (s:OpenSensation:Sensor {slug: 'lidar'})-[r:LIMITED_BY]->(l:Limitation)
RETURN s.name, l.name, r.evidence_status, r.conditions;

Synchronize Neo4j

Set NEO4J_URI, NEO4J_USERNAME, and NEO4J_PASSWORD in the execution environment. Use encrypted neo4j+s:// for AuraDB. Run:

python -m scripts.sync_graph

The driver verifies connectivity, creates the identifier uniqueness constraint, and applies a serialized transaction. It merges nodes and edges, removes stale OpenSensation-owned edges, and marks removed nodes retired=true to preserve user-owned external relationships. It never deletes another namespace. Query coalesce(n.retired, false) = false when selecting current nodes.

The standalone Cypher export is an idempotent merge script; it does not reconcile removals. Use the synchronization command for a changing projection. Graph data is a knowledge representation, not a sensor-fusion or causal-inference engine.