From 78c9961f36f5c5347be9d59b9320d051d3bd410f Mon Sep 17 00:00:00 2001 From: marcel Date: Sun, 7 Jul 2024 20:33:21 +0200 Subject: [PATCH] Add subComponentRelationship type to entity file In the datamodel the entity.ttl file describes the "static" knowledge about attributes. For instance the domain and range of attributes. In this PR, the Relationships will be typed as subcomponents and peer relationships. Related Epics: #514 Related User-stories: #555 Signed-off-by: marcel --- semantic-model/datamodel/README.md | 54 +++++++- .../datamodel/examples/context.jsonld | 4 + .../datamodel/tools/lib/owlUtils.js | 20 +++ .../datamodel/tools/tests/schema2owl/c0 | 4 + .../tests/schema2owl/schema3_c0.json_result | 3 +- .../tools/tests/schema2owl/schema4_c0.json | 125 ++++++++++++++++++ .../tools/tests/schema2owl/schema4_c0.json_id | 1 + .../tests/schema2owl/schema4_c0.json_result | 17 +++ .../datamodel/tools/tests/testOwlUtils.js | 4 +- semantic-model/datamodel/tools/validate.js | 2 +- 10 files changed, 229 insertions(+), 5 deletions(-) create mode 100644 semantic-model/datamodel/tools/tests/schema2owl/schema4_c0.json create mode 100644 semantic-model/datamodel/tools/tests/schema2owl/schema4_c0.json_id create mode 100644 semantic-model/datamodel/tools/tests/schema2owl/schema4_c0.json_result diff --git a/semantic-model/datamodel/README.md b/semantic-model/datamodel/README.md index 3133a84b..4933e000 100644 --- a/semantic-model/datamodel/README.md +++ b/semantic-model/datamodel/README.md @@ -294,7 +294,7 @@ In order to validate it with a JSON-Schema, first the base object must be descri } ``` This specifies the mandatory `id` and `type` field of every NGSI-LD object. `id` must be an *URN* and `type` must contain a *compacted* form. -The "$schema" field must be "https://json-schema.org/draft/2020-12/schema", the $id of the schema must be a valid URL with the additional constraint that all '#' fields **must be URL-Encoded**. An example for a schema and related data can be seen in the following: +The `$schema` field must be https://json-schema.org/draft/2020-12/schema, the `$id` of the schema must be a valid URL with the additional constraint that all '#' fields **must be URL-Encoded**. An example for a schema and related data can be seen in the following: ```json # JSON-Schema: @@ -445,7 +445,8 @@ The following JSON-Schema Keywords from the standard are forbidded: ## Added JSON-Schmea Keywords: The following JSON-Schema Keywords are added to the standard: -* **relationship:** Contains the *compacted* type for a NGSI-LD relationship. +* **relationship:** Contains the *compacted* type for a NGSI-LD relationship. +* **relationship_type** is further detailing the relationship type. There are two values allowed: `subcomponent` or `peer` ## Integrating ECLASS Properties `ECLASS` provides additional data for every `IRDI` which can be added/mapped to `JSON-Schema`: @@ -584,6 +585,55 @@ node tools/jsonschema2shacl.js -s examples/plasmacutter_schema.json -i https:// ``` +### Create OWL Entities descriptions + +The `entities.ttl` description contains all non validation relevant information about entities. It defines **domain** and **range** of attributes and more details, e.g. whether a NGSI-LD Relationship is a subcomponent or a peer relationship. The description is created in [OWL](https://www.w3.org/OWL/). + +#### Install + +``` +npm install +``` + +#### Usage + +``` +jsonschema2owl.js + +Converting an IFF Schema file for NGSI-LD objects into an OWL entity file. + +Options: + --version Show version number [boolean] + -s, --schema Schema File containing array of Schemas [string] [required] + -i, --schemaid Schma-id of object to generate SHACL for [string] [required] + -c, --context JSON-LD-Context [string] [required] + -n, --namespace default namespace (if not derived by context) [string] + -h, --help Show help [boolean] +``` + +#### Examples +``` +node ./tools/jsonschema2owl.js -s ./examples/filter_and_cartridge_subcomponent_schema.json -c file:$PWD/./examples/context.jsonld -i https://industry-fusion.org/eclass#0173-1#01-ACK991#016 +``` +``` +@prefix owl: . +@prefix iffb: . +@prefix rdfs: . +@prefix ngsi-ld: . +@prefix b: . + +iffb:hasCartridge + a owl:Property, b:SubComponentRelationship; + rdfs:domain ; + rdfs:range ngsi-ld:Relationship. +iffb:machine_state + a owl:Property; + rdfs:domain ; + rdfs:range ngsi-ld:Property. + a owl:Class. + +``` + ### Convert NGSI-LD forms #### Install diff --git a/semantic-model/datamodel/examples/context.jsonld b/semantic-model/datamodel/examples/context.jsonld index 6d7e122e..b21818d9 100644 --- a/semantic-model/datamodel/examples/context.jsonld +++ b/semantic-model/datamodel/examples/context.jsonld @@ -33,6 +33,10 @@ "sh": { "@id": "http://www.w3.org/ns/shacl#", "@prefix": true + }, + "base": { + "@id": "https://industryfusion.github.io/contexts/ontology/v0/base/", + "@prefix": true } } ] diff --git a/semantic-model/datamodel/tools/lib/owlUtils.js b/semantic-model/datamodel/tools/lib/owlUtils.js index d108acfd..0f90a7de 100644 --- a/semantic-model/datamodel/tools/lib/owlUtils.js +++ b/semantic-model/datamodel/tools/lib/owlUtils.js @@ -21,6 +21,7 @@ const RDF = $rdf.Namespace('http://www.w3.org/1999/02/22-rdf-syntax-ns#'); const RDFS = $rdf.Namespace('http://www.w3.org/2000/01/rdf-schema#'); const OWL = $rdf.Namespace('http://www.w3.org/2002/07/owl#'); const NGSILD = $rdf.Namespace('https://uri.etsi.org/ngsi-ld/'); +const BASE = $rdf.Namespace('https://industryfusion.github.io/contexts/ontology/v0/base/'); const globalAttributes = []; const globalEntities = []; @@ -40,6 +41,7 @@ class Attribute { this._entities = []; this._attributeName = attributeName; this._isProperty = true; + this._isSubcomponent = false; } addEntity (entity) { @@ -61,6 +63,14 @@ class Attribute { get entities () { return this._entities; } + + get isSubcomponent () { + return this._isSubcomponent; + } + + set isSubcomponent (isSc) { + this._isSubcomponent = isSc; + } } function dumpAttribute (attribute, entity, store) { @@ -71,6 +81,11 @@ function dumpAttribute (attribute, entity, store) { store.add($rdf.sym(attribute.attributeName), RDFS('range'), NGSILD('Property')); } else { store.add($rdf.sym(attribute.attributeName), RDFS('range'), NGSILD('Relationship')); + if (attribute.isSubcomponent) { + store.add($rdf.sym(attribute.attributeName), RDF('type'), BASE('SubComponentRelationship')); + } else { + store.add($rdf.sym(attribute.attributeName), RDF('type'), BASE('PeerRelationship')); + } } } } @@ -103,6 +118,10 @@ function scanProperties (entity, typeschema, contextManager) { if ('relationship' in typeschema.properties[property]) { isProperty = false; } + let isSubcomponent = false; + if ('relationship_type' in typeschema.properties[property] && typeschema.properties[property].relationship_type === 'subcomponent') { + isSubcomponent = true; + } let path = property; if (!contextManager.isValidIri(path)) { path = contextManager.expandTerm(path); @@ -111,6 +130,7 @@ function scanProperties (entity, typeschema, contextManager) { const attribute = new Attribute(path); attribute.addEntity(entity); attribute.isProperty = isProperty; + attribute.isSubcomponent = isSubcomponent; globalAttributes.push(attribute); }); } diff --git a/semantic-model/datamodel/tools/tests/schema2owl/c0 b/semantic-model/datamodel/tools/tests/schema2owl/c0 index 6d7e122e..b21818d9 100644 --- a/semantic-model/datamodel/tools/tests/schema2owl/c0 +++ b/semantic-model/datamodel/tools/tests/schema2owl/c0 @@ -33,6 +33,10 @@ "sh": { "@id": "http://www.w3.org/ns/shacl#", "@prefix": true + }, + "base": { + "@id": "https://industryfusion.github.io/contexts/ontology/v0/base/", + "@prefix": true } } ] diff --git a/semantic-model/datamodel/tools/tests/schema2owl/schema3_c0.json_result b/semantic-model/datamodel/tools/tests/schema2owl/schema3_c0.json_result index eabb3a7d..db62cd49 100644 --- a/semantic-model/datamodel/tools/tests/schema2owl/schema3_c0.json_result +++ b/semantic-model/datamodel/tools/tests/schema2owl/schema3_c0.json_result @@ -1,10 +1,11 @@ @prefix owl: . @prefix iffb: . @prefix rdfs: . +@prefix base: . @prefix ngsi-ld: . iffb:hasFilter - a owl:Property; + a owl:Property, base:PeerRelationship; rdfs:domain ; rdfs:range ngsi-ld:Relationship. iffb:machine_state diff --git a/semantic-model/datamodel/tools/tests/schema2owl/schema4_c0.json b/semantic-model/datamodel/tools/tests/schema2owl/schema4_c0.json new file mode 100644 index 00000000..d4df1dd4 --- /dev/null +++ b/semantic-model/datamodel/tools/tests/schema2owl/schema4_c0.json @@ -0,0 +1,125 @@ +[ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://industry-fusion.org/eclass%230173-1%2301-ACK991%23016", + "title": "Filter", + "description": "Schweissrauchabsauger", + "type": "object", + "properties": { + "type": { + "const": "eclass:0173-1#01-ACK991#016" + }, + "id": { + "type": "string", + "pattern": "^urn:[a-zA-Z0-9][a-zA-Z0-9-]{1,31}:([a-zA-Z0-9()+,.:=@;$_!*'-]|%[0-9a-fA-F]{2})*$" + } + }, + "required": ["type", "id"], + "allOf": [ + { + "$ref": "https://industry-fusion.org/base-objects/v0.1/machine/properties" + }, + { + "$ref": "https://industry-fusion.org/base-objects/v0.1/filter/relationships" + } + ] + }, + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://industry-fusion.org/base-objects/v0.1/filter/relationships", + "title": "IFF template for filter relationship", + "description": "Cutter template for IFF", + "type": "object", + "properties": { + "hasCartridge": { + "relationship": "eclass:0173-1#01-AKE795#017", + "relationship_type": "subcomponent", + "$ref": "https://industry-fusion.org/base-objects/v0.1/link" + } + } + }, + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://industry-fusion.org/base-objects/v0.1/machine/properties", + "title": "Cutter properties", + "description": "Properties for class cutter", + "type": "object", + "properties": { + "machine_state": { + "type": "string", + "title": "Machine Status", + "description": "Current status of the machine (Online_Idle, Run, Online_Error, Online_Maintenance, Setup, Testing)", + "enum": [ + "Online_Idle", + "Run", + "Online_Error", + "Online_Maintenance", + "Setup", + "Testing" + ] + } + }, + "required": [ + "machine_state" + ] + }, + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://industry-fusion.org/eclass%230173-1%2301-AKE795%23017", + "title": "Filterpatrone", + "description": "Filterpatrone", + "type": "object", + "properties": { + "type": { + "const": "eclass:0173-1#01-AKE795#017" + }, + "id": { + "type": "string", + "pattern": "^urn:[a-zA-Z0-9][a-zA-Z0-9-]{1,31}:([a-zA-Z0-9()+,.:=@;$_!*'-]|%[0-9a-fA-F]{2})*$" + } + }, + "required": ["type", "id"], + "allOf": [ + { + "$ref": "https://industry-fusion.org/base-objects/v0.1/cartridge/properties" + } + ] + }, + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://industry-fusion.org/base-objects/v0.1/cartridge/properties", + "title": "Cartridge properties", + "description": "Properties for class cutter", + "type": "object", + "properties": { + "waste_class": { + "type": "string", + "title": "Waste Class", + "description": "Current wasteclass of the cartridge (WC0, WC1, WC2, WC3)", + "enum": [ + "WC0", + "WC1", + "WC2", + "WC3" + ] + } + }, + "required": [ + "waste_class" + ] + }, + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://industry-fusion.org/base-objects/v0.1/link", + "title": "IFF template for cutter relationship", + "description": "Cutter template for IFF", + "type": "object", + "properties": { + "object": { + "type": "string", + "pattern": "^urn:[a-zA-Z0-9][a-zA-Z0-9-]{0,31}:[a-zA-Z0-9()+,\\-.:=@;$_!*']*[a-zA-Z0-9()+,\\-.:=@;$_!*']$" + } + }, + "required": ["object"] + } +] \ No newline at end of file diff --git a/semantic-model/datamodel/tools/tests/schema2owl/schema4_c0.json_id b/semantic-model/datamodel/tools/tests/schema2owl/schema4_c0.json_id new file mode 100644 index 00000000..a3451f15 --- /dev/null +++ b/semantic-model/datamodel/tools/tests/schema2owl/schema4_c0.json_id @@ -0,0 +1 @@ +https://industry-fusion.org/eclass#0173-1#01-ACK991#016 diff --git a/semantic-model/datamodel/tools/tests/schema2owl/schema4_c0.json_result b/semantic-model/datamodel/tools/tests/schema2owl/schema4_c0.json_result new file mode 100644 index 00000000..439d1a67 --- /dev/null +++ b/semantic-model/datamodel/tools/tests/schema2owl/schema4_c0.json_result @@ -0,0 +1,17 @@ +@prefix owl: . +@prefix iffb: . +@prefix rdfs: . +@prefix base: . +@prefix ngsi-ld: . + +iffb:hasCartridge + a owl:Property, base:SubComponentRelationship; + rdfs:domain ; + rdfs:range ngsi-ld:Relationship. +iffb:machine_state + a owl:Property; + rdfs:domain ; + rdfs:range ngsi-ld:Property. + a owl:Class. + + diff --git a/semantic-model/datamodel/tools/tests/testOwlUtils.js b/semantic-model/datamodel/tools/tests/testOwlUtils.js index ea469727..20d3afd3 100644 --- a/semantic-model/datamodel/tools/tests/testOwlUtils.js +++ b/semantic-model/datamodel/tools/tests/testOwlUtils.js @@ -26,6 +26,7 @@ const RDF = $rdf.Namespace('http://www.w3.org/1999/02/22-rdf-syntax-ns#'); const RDFS = $rdf.Namespace('http://www.w3.org/2000/01/rdf-schema#'); const OWL = $rdf.Namespace('http://www.w3.org/2002/07/owl#'); const NGSILD = $rdf.Namespace('https://uri.etsi.org/ngsi-ld/'); +const BASE = $rdf.Namespace('https://industryfusion.github.io/contexts/ontology/v0/base/'); describe('Test class Entity', function () { it('Should create Entity with IRI', function () { @@ -75,7 +76,8 @@ describe('Test dumpAttribute', function () { const expected = [ [$rdf.namedNode(attributeName), RDF('type'), OWL('Property')], [$rdf.namedNode(attributeName), RDFS('domain'), $rdf.namedNode(entityName)], - [$rdf.namedNode(attributeName), RDFS('range'), NGSILD('Relationship')] + [$rdf.namedNode(attributeName), RDFS('range'), NGSILD('Relationship')], + [$rdf.namedNode(attributeName), RDF('type'), BASE('PeerRelationship')] ]; const store = { add: (s, p, o) => { added.push([s, p, o]); } diff --git a/semantic-model/datamodel/tools/validate.js b/semantic-model/datamodel/tools/validate.js index 56e21d1b..800846a2 100644 --- a/semantic-model/datamodel/tools/validate.js +++ b/semantic-model/datamodel/tools/validate.js @@ -25,7 +25,7 @@ const removedKeywords = [ 'valid', 'error', 'annotation', 'additionalProperties', 'propertyNames', '$vocabulary', '$defs', 'multipleOf', 'uniqueItems', 'maxContains', 'minContains', 'maxProperties', 'minPropeties', 'dependentRequired']; -const addedKeywords = ['relationship', 'datatype', 'unit']; +const addedKeywords = ['relationship', 'relationship_type', 'datatype', 'unit']; const argv = yargs .command('$0', 'Validate a JSON-LD object with IFF Schema.')