Refactoring Ontology Attributes (Component Node Pattern)

Status: implemented (confirmed during documentation review). The OntologyAttribute component-node pattern described below is live in code: HAS_ATTRIBUTE and HAS_SOURCE relationships are used throughout Neo4jEntityOntology.cs, Neo4jOntology.cs, ontology_service.py, ontology_io_service.py, KnowledgeGraphEditor.py, OntologySynchroniser.py, and PostgresOntologyExtractor.py. INeo4jOntologyAttribute.cs exists as described in Step 2. This document is retained as the design rationale and implementation record for the pattern, not as an open proposal.

1. Background & Current Architecture

Currently, dynamic attributes for EntityOntology and RelationshipOntology are stored as flat, scalar properties directly on the Neo4j ontology nodes. Type and value information is serialized into a string (e.g., SET n.FirstName = "(string) John").

The Problem: Neo4j node properties only support basic primitive types or primitive arrays. There is no structured way to attach complex metadata—such as Sources—to an attribute. When users create tags via the multi-select component in OntologyElement.razor, the sources are held in memory but dropped during the save process because the flat dictionary structure (Dictionary<string, object?>) cannot represent them.

2. Target Architecture (Option 3)

We are adopting a graph-idiomatic component node pattern. Attributes will no longer be stored as properties on the main ontology nodes. Instead, they will be spun out into their own nodes, labeled OntologyAttribute.

Relationships: - (o:EntityOntology)-[:HAS_ATTRIBUTE]->(a:OntologyAttribute) - (o:RelationshipOntology)-[:HAS_ATTRIBUTE]->(a:OntologyAttribute) - (a:OntologyAttribute)-[:HAS_SOURCE]->(s:OntologySource)

The OntologyAttribute node will have standard properties like: name, type, and value.


3. Implementation Steps

Step 1: Generate the Migration Script

Create a Cypher script to migrate existing data before changing the codebase. - Iterates over all :EntityOntology and :RelationshipOntology nodes. - Extracts dynamic properties (filtering out standard properties like id, entity_description, entity_source_labels, entity_target_labels). - Creates new :OntologyAttribute nodes for each extracted property and parses out the type and value from the string representation "(type) value". - Connects the parent node to the new attribute node via a [:HAS_ATTRIBUTE] relationship. - Deletes the legacy flat properties from the parent nodes.

Step 2: Update the Neo4j Model & ORM

Create the new structural entities to represent attributes in code. - Interfaces: Create INeo4jOntologyAttribute.cs containing Name, Type, Value, and a list of sources. - Classes: Create Neo4jOntologyAttribute.cs implementing the interface. - Update Ontology Entities: Modify INeo4jEntityOntology and Neo4jEntityOntology to replace Dictionary<string, object?> Attributes with an IEnumerable<INeo4jOntologyAttribute> Attributes collection.

Step 3: Update the Codebase & Cypher Queries

Update all data access layers that utilize this data. - Neo4jOntologies.cs (Read): Update GetEntities and the Initialize methods of Neo4jEntityOntology. They must now perform a MATCH (n)-[:HAS_ATTRIBUTE]->(a:OntologyAttribute) to hydrate the attribute list instead of examining node.Properties. - Neo4jOntologies.cs (Write): Update the Create and Save methods. Instead of $setClauses assigning node properties, execute nested Cypher or separate queries to MERGE or CREATE the connected :OntologyAttribute nodes. - Delete Handling: Remove or detach-delete old attributes that the user deleted in the UI.

Step 4: Add Source Connections

Wire up the OntologySource entities to the newly created OntologyAttribute entities. - ORM Save Process: During attribute save, include logic to reconcile [:HAS_SOURCE] relationships to existing :OntologySource nodes based on the selected tags. - UI Logic Update: In OntologyElement.razor, update the mapping logic. When OnSave() is triggered, correctly translate the UI AttributeData.Sources list into the INeo4jOntologyAttribute.Sources structure so it propagates to the service layer natively. - ORM Load Process: When fetching the attributes, optionally expand the MATCH to OPTIONAL MATCH (a)-[:HAS_SOURCE]->(s:OntologySource) and populate the attribute's source list.