A proof-of-concept library for mapping Asset Administration Shell (AAS) data to a Neo4j graph database, including bidirectional serialization and a query language compiler (AASQL → Cypher).
aas_mapping/
├── aas_neo4j_adapter/
│ ├── aas_neo4j_client.py # Main API: AASNeo4JClient
│ ├── base.py # BaseNeo4JClient, Neo4jModelConfig
│ ├── utils.py # UploadStats, hash helpers
│ ├── validation.py # AASConstraintChecker — spec constraint validation
│ ├── neo_aas_object_store.py # basyx-sdk AbstractObjectStore for Neo4j
│ ├── jsonification/
│ │ ├── neo4j_import.py # JSON → Neo4j (JsonToNeo4jImporter)
│ │ └── neo4j_export.py # Neo4j → JSON (JsonFromNeo4jExporter)
│ └── querification/
│ ├── aasql_to_ast.py # AASQL JSON → AST (parser)
│ ├── ast_nodes.py # AST node type definitions
│ ├── ast_to_cypher.py # AST → Cypher (compiler)
│ └── aasql_to_cypher.py # Entry point: convert_aasql_to_cypher()
├── examples/
│ ├── queries/ # Example AASQL queries (JSON)
│ ├── ast/ # Expected AST representations
│ ├── cypher/ # Generated Cypher scripts
│ └── submodels/ # IDTA template submodel JSON files
└── test/
├── test_aasql2ast.py # Unit tests for AASQL → AST parsing
├── test_roundtrip.py # Integration: JSON and XML ↔ Neo4j round-trip tests
└── test_constraint_checker.py # Unit + integration tests for AASConstraintChecker
AAS JSON file
└─→ AASNeo4JClient.upload_json_file()
└─→ JsonToNeo4jImporter._process_dict()
├─→ Scalars become node properties
├─→ Dicts become child nodes (CHILD relationship)
└─→ Lists become multiple nodes with list_index tracking
└─→ APOC batch create nodes + relationships
Neo4j subgraph
└─→ JsonFromNeo4jExporter.convert_subgraph_to_data_dict()
├─→ Rebuilds nested dict structure from relationships
└─→ Reverses list-of-dicts flattening
AASQL JSON query
└─→ parse_aasql_query() # aasql_to_ast.py
└─→ AST (ast_nodes.py)
└─→ converter() # ast_to_cypher.py
└─→ MATCH ... WHERE ... RETURN Cypher string
| AAS concept | Neo4j representation |
|---|---|
Referable |
Node |
AssetInformation |
Node |
| Containment (Referable → Referable) | CHILD relationship |
Reference |
REFERENCES relationship |
| Scalar property | Node property |
Each node carries its full AAS class hierarchy as labels. For example, a Property node gets labels:
:Property:DataElement:SubmodelElement:Referable:Qualifiable
This allows Cypher queries to match on any level of the hierarchy.
Neo4j does not support dict-valued properties. Lists of dicts (e.g., keys = [{"type": "...", "value": "..."}]) are stored as parallel lists:
keys_type = ["GlobalReference", ...]
keys_value = ["0173-1#01-AAO677#002", ...]
The shared positional index enables reconstruction during export.
Reference and ConceptDescription nodes are deduplicated using SHA256 hashing of their properties. This prevents duplicate semantic identifiers across multiple AAS imports.
AASQL queries are JSON documents compiled to Cypher via convert_aasql_to_cypher().
| AASQL root | Cypher pattern |
|---|---|
$aas |
(aas:AssetAdministrationShell) |
$sm |
(sm:Submodel) |
$cd |
(cd:ConceptDescription) |
$sme.<idShort> |
SubmodelElement traversal by idShort |
$<root>#<attribute>[.<nested>]
Examples: $aas#idShort, $aas#assetInformation.assetType, $sme.Color#value
| Category | Operators |
|---|---|
| Comparison | $eq, $ne, $gt, $ge, $lt, $le |
| String | $contains, $starts-with, $ends-with, $regex |
| Logical | $and, $or, $not |
| List match | $match (all conditions on same list element) |
| Type casts | $strCast, $numCast, $hexCast, $boolCast, $dateTimeCast, $timeCast |
AASQL query:
{
"$condition": {
"$and": [
{ "$eq": [{ "$field": "$sme.Color#value" }, { "$strVal": "Blue" }] },
{ "$gt": [{ "$field": "$sme.Size#value" }, { "$numVal": 50 }] }
]
}
}Generated Cypher:
MATCH (sme_Color:SubmodelElement {idShort: "Color"})
MATCH (sme_Size:SubmodelElement {idShort: "Size"})
WHERE sme_Color.value IN ["Blue"] AND sme_Size.value > 50
RETURN sme_Color, sme_Size- Python 3.10+
- Neo4j Community Edition (tested with 5.26.x)
- APOC plugin enabled in Neo4j
pip install .# Linux/Mac
$NEO4J_HOME/bin/neo4j console
# Windows
%NEO4J_HOME%\bin\neo4j consoleDefault bolt URI: bolt://localhost:7687
from aas_mapping.aas_neo4j_adapter.aas_neo4j_client import AASNeo4JClient, AAS_NEO4J_MODEL_CONFIG
client = AASNeo4JClient(
uri="bolt://localhost:7687",
user="neo4j",
password="12345678",
model_config=AAS_NEO4J_MODEL_CONFIG
)
client.upload_json_file("path/to/your_aas.json")MATCH (n) RETURN n;from aas_mapping.aas_neo4j_adapter.querification.aasql_to_cypher import convert_aasql_to_cypher
query = {
"$condition": {
"$eq": [{"$field": "$aas#idShort"}, {"$strVal": "MyShell"}]
}
}
cypher = convert_aasql_to_cypher(query)
print(cypher)uv run pytest aas_mapping/test/Integration tests require a live Neo4j instance (bolt://localhost:7687, user neo4j, password 12345678). They are skipped automatically if Neo4j is unreachable.
AASConstraintChecker validates AAS data already loaded in Neo4j against the AAS specification constraints. It runs Cypher queries and returns structured ConstraintViolation records grouped in a ConstraintReport.
from aas_mapping.aas_neo4j_adapter.validation import AASConstraintChecker
checker = AASConstraintChecker(
uri="bolt://localhost:7687",
user="neo4j",
password="12345678",
)
report = checker.check_all()
print(report.summary())| Constraint | Description |
|---|---|
| AASd-002 | idShort must match [a-zA-Z][a-zA-Z0-9_-]*[a-zA-Z0-9_]+ |
| AASd-005 | revision requires version in AdministrativeInformation |
| AASd-014 | SelfManagedEntity must have globalAssetId or specificAssetId |
| AASd-021 | Qualifier type must be unique within a Qualifiable |
| AASd-022 | idShort must be unique within the parent namespace |
| AASd-077 | Extension name must be unique within a parent |
| AASd-107 | SubmodelElementList child semanticId must match semanticIdListElement |
| AASd-108 | SubmodelElementList children must match typeValueListElement |
| AASd-109 | valueTypeListElement required when typeValueListElement is Property/Range |
| AASd-114 | All SubmodelElementList children with semanticId must share the same one |
| AASd-117 | Non-Identifiable Referables not under SubmodelElementList must have idShort |
| AASd-118 | supplementalSemanticIds requires a semanticId |
| AASd-119 | TemplateQualifier requires element kind = "Template" |
| AASd-121 – 129 | Reference key type and ordering constraints |
| AASd-131 | AssetInformation must have globalAssetId or at least one specificAssetId |
| AASd-133 | SpecificAssetId.externalSubjectId must be an ExternalReference |
| AASd-134 | Operation variable idShorts must be unique across in/out/inout |
- Export is partial:
JsonFromNeo4jExporterreconstructs basic nested structures but does not fully restore all AAS-specific semantics. - Reference resolution:
ModelReferenceandExternalReferencetargets are stored but not resolved/traversed automatically. - ECLASS: ECLASS classifications are stored as properties; they are not modeled as a separate node graph.
- No authentication management: Connection credentials are passed directly; no secrets management is included.
- Deduplication ignores
referredSemanticId: The SHA256 hash used to deduplicateReferencenodes covers only flat properties. Two References that differ solely in theirreferredSemanticIdrelationship are collapsed into one node, which may create graph cycles. The exporter detects and breaks these cycles with a warning, but round-trip fidelity for such elements is lost.