diff --git a/.github/workflows/pull_request_tester.yml b/.github/workflows/pull_request_tester.yml
new file mode 100644
index 00000000..bd29cde7
--- /dev/null
+++ b/.github/workflows/pull_request_tester.yml
@@ -0,0 +1,31 @@
+name: "Run tests (required)"
+on:
+ workflow_dispatch:
+ pull_request:
+ push:
+ branches:
+ - main
+
+jobs:
+ dotnet-tests:
+ runs-on: ubuntu-latest
+ name: ".NET Tests & Reports"
+ timeout-minutes: 5
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: 8.0.x
+
+ - name: Restore dependencies
+ run: dotnet restore ./client/Boundaries/Boundaries.sln
+
+ - name: Build
+ run: dotnet build ./client/Boundaries/Boundaries.sln --no-restore
+
+ - name: Test
+ run: |
+ dotnet test ./client/Boundaries/Boundaries.sln --no-build --verbosity normal
diff --git a/.gitignore b/.gitignore
index 82762c50..46acac51 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,3 +4,5 @@ dexpi.properties
pandid.xml
pandid.trig
imf-ontology.owl.ttl
+rml/segments.trig
+client/.idea
diff --git a/client/Boundaries/Boundaries.sln b/client/Boundaries/Boundaries.sln
new file mode 100644
index 00000000..d70a8cbd
--- /dev/null
+++ b/client/Boundaries/Boundaries.sln
@@ -0,0 +1,22 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Boundaries", "Boundaries\Boundaries.csproj", "{B7723633-9BCD-44C6-BC90-1E3F5C6C8101}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestBoundaries", "TestBoundaries\TestBoundaries.csproj", "{6389698E-C0E2-4262-AE30-FD2A01654FA5}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {B7723633-9BCD-44C6-BC90-1E3F5C6C8101}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B7723633-9BCD-44C6-BC90-1E3F5C6C8101}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B7723633-9BCD-44C6-BC90-1E3F5C6C8101}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B7723633-9BCD-44C6-BC90-1E3F5C6C8101}.Release|Any CPU.Build.0 = Release|Any CPU
+ {6389698E-C0E2-4262-AE30-FD2A01654FA5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {6389698E-C0E2-4262-AE30-FD2A01654FA5}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6389698E-C0E2-4262-AE30-FD2A01654FA5}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {6389698E-C0E2-4262-AE30-FD2A01654FA5}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+EndGlobal
diff --git a/client/Boundaries/Boundaries/Boundaries.csproj b/client/Boundaries/Boundaries/Boundaries.csproj
new file mode 100644
index 00000000..da63e1e5
--- /dev/null
+++ b/client/Boundaries/Boundaries/Boundaries.csproj
@@ -0,0 +1,14 @@
+
+
+
+ Exe
+ net8.0
+ enable
+ enable
+
+
+
+
+
+
+
diff --git a/client/Boundaries/Boundaries/DatalogCreator.cs b/client/Boundaries/Boundaries/DatalogCreator.cs
new file mode 100644
index 00000000..2f278275
--- /dev/null
+++ b/client/Boundaries/Boundaries/DatalogCreator.cs
@@ -0,0 +1,32 @@
+using IriTools;
+
+namespace Boundaries;
+
+public class DatalogCreator
+{
+ public IriReference BoundaryGraph = new IriReference($"https://data.equinor.com/boundaries/{Guid.NewGuid()}");
+
+ public string CreateCommissioningSparqlQuery()
+ {
+ return $"select ?s(GROUP_CONCAT(?label; SEPARATOR=',') AS ?labels) where {{?s a <{BoundaryGraph}>; rdfs:label ?label. OPTIONAL{{?s ?tag.}} }} GROUP BY (?s)";
+ }
+
+ public string CreateBoundaryDatalogRule(string internalComponentLabel, IriReference[] borderComponentIris)
+ {
+ var filters = borderComponentIris
+ .Select(iri => $"NOT FILTER(?node1 = <{iri}>)")
+ .Aggregate("", (acc, filter) => acc + ",\n " + filter);
+ return $$"""
+
+ <{{BoundaryGraph}}> [?node] :-
+ rdfs:label [?internal, "{{internalComponentLabel}}"],
+ imf:connectedTo [?internal, ?node],
+ dexpi:PipingOrEquipment [?node].
+
+ <{{BoundaryGraph}}> [?node] :-
+ <{{BoundaryGraph}}> [?node1],
+ imf:connectedTo [?node1, ?node]{{filters}},
+ dexpi:PipingOrEquipment [?node].
+ """;
+ }
+}
\ No newline at end of file
diff --git a/client/Boundaries/Boundaries/Program.cs b/client/Boundaries/Boundaries/Program.cs
new file mode 100644
index 00000000..8176c249
--- /dev/null
+++ b/client/Boundaries/Boundaries/Program.cs
@@ -0,0 +1,45 @@
+using IriTools;
+
+namespace Boundaries;
+
+internal class Program
+{
+ private static async Task Main(string[] args)
+ {
+ if (args.Length < 3)
+ {
+ Console.WriteLine(
+ "Usage \"dotnet run filename-for-dexpi-in-rdf \"label-of-internal-componenet\" iri-of-border-component ... iri-of-border-component ");
+ Console.WriteLine(
+ "For example \"dotnet run ../../../rml/pandid.trig \"T4750\" https://assetid.equinor.com/plantx#Nozzle-12 https://assetid.equinor.com/plantx#Nozzle-8 https://assetid.equinor.com/plantx#PlateHeatExchanger-1 https://assetid.equinor.com/plantx#ReciprocatingPump-1");
+ return;
+ }
+
+ var dexpiFilePath = args[0];
+ var internalComponentLabel = args[1];
+ if (!File.Exists(dexpiFilePath))
+ {
+ Console.WriteLine(
+ $"Could not find one of the input file {dexpiFilePath}");
+ return;
+ }
+
+ var borderComponentIris = args.Skip(2).Select(iri => new IriReference(iri)).ToArray();
+ var datalogCreator = new DatalogCreator();
+ var datalog = datalogCreator.CreateBoundaryDatalogRule(internalComponentLabel, borderComponentIris);
+ var conn = RdfoxApi.GetDefaultConnectionSettings();
+ await RdfoxApi.LoadDatalog(conn, datalog);
+
+ var data = File.ReadAllText(dexpiFilePath);
+ await RdfoxApi.LoadData(conn, data);
+
+ var queryString = datalogCreator.CreateCommissioningSparqlQuery();
+ var result = await RdfoxApi.QuerySparql(conn, queryString);
+ Console.WriteLine("Commissioning package:");
+ Console.WriteLine(result);
+
+ await RdfoxApi.DeleteData(conn, data);
+ await RdfoxApi.DeleteDatalog(conn, datalog);
+
+ }
+}
\ No newline at end of file
diff --git a/client/Boundaries/Boundaries/RdfoxApi.cs b/client/Boundaries/Boundaries/RdfoxApi.cs
new file mode 100644
index 00000000..dd277962
--- /dev/null
+++ b/client/Boundaries/Boundaries/RdfoxApi.cs
@@ -0,0 +1,148 @@
+using System.Text;
+using IriTools;
+
+namespace Boundaries;
+
+
+public class RdfoxApi
+{
+ public struct ConnectionSettings
+ {
+ public string Host { get; set; }
+ public int Port { get; set; }
+ public string Username { get; set; }
+ public string Password { get; set; }
+ public string Datastore { get; set; }
+
+ }
+
+ public static ConnectionSettings GetDefaultConnectionSettings()
+ {
+ return new ConnectionSettings
+ {
+ Host = "localhost",
+ Port = 12110,
+ Username = "admin",
+ Password = "admin",
+ Datastore = "boundaries"
+ };
+ }
+
+
+ ///
+ /// curl -i -X POST localhost:12110/datastores/boundaries/content?operation=delete-content -H "Content-Type: application/x.datalog" -T boundaries.dlog
+ ///
+ ///
+ ///
+ public static async Task DeleteDatalog(ConnectionSettings conn, string datalog)
+ {
+ using (var client = new HttpClient())
+ {
+ var uri = new Uri($"http://{conn.Host}:{conn.Port}/datastores/{conn.Datastore}/content?operation=delete-content");
+ var content = new StringContent(datalog, Encoding.UTF8, "application/x.datalog");
+
+ var request = new HttpRequestMessage(HttpMethod.Patch, uri)
+ {
+ Content = content
+ };
+
+ var response = await client.SendAsync(request);
+ response.EnsureSuccessStatusCode();
+ }
+ }
+
+ ///
+ /// curl -i -X POST localhost:12110/datastores/boundaries/content -H "Content-Type: application/x.datalog" -T boundaries.dlog
+ ///
+ ///
+ ///
+ public static async Task LoadDatalog(ConnectionSettings conn, string datalog)
+ {
+ using (var client = new HttpClient())
+ {
+ var uri = new Uri($"http://{conn.Host}:{conn.Port}/datastores/{conn.Datastore}/content");
+ var content = new StringContent(datalog, Encoding.UTF8, "application/x.datalog");
+
+ var request = new HttpRequestMessage(HttpMethod.Post, uri)
+ {
+ Content = content
+ };
+
+
+ var response = await client.SendAsync(request);
+ response.EnsureSuccessStatusCode();
+ }
+ }
+
+ ///
+ /// curl -i -X PATCH localhost:12110/datastores/boundaries/content?operation=delete-content -H "Content-Type: application/trig" -T boundaries.dlog
+ ///
+ ///
+ ///
+ public static async Task DeleteData(ConnectionSettings conn, string data)
+ {
+ using (var client = new HttpClient())
+ {
+ var uri = new Uri($"http://{conn.Host}:{conn.Port}/datastores/{conn.Datastore}/content?operation=delete-content");
+ var content = new StringContent(data, Encoding.UTF8, "application/trig");
+
+ var request = new HttpRequestMessage(HttpMethod.Patch, uri)
+ {
+ Content = content
+ };
+
+
+ var response = await client.SendAsync(request);
+ response.EnsureSuccessStatusCode();
+ }
+ }
+
+ ///
+ /// curl -i -X POST localhost:12110/datastores/boundaries/content -H "Content-Type: application/trig" -T boundaries.dlog
+ ///
+ ///
+ ///
+ public static async Task LoadData(ConnectionSettings conn, string data)
+ {
+ using (var client = new HttpClient())
+ {
+ var uri = new Uri($"http://{conn.Host}:{conn.Port}/datastores/{conn.Datastore}/content");
+ var content = new StringContent(data, Encoding.UTF8, "application/trig");
+
+ var request = new HttpRequestMessage(HttpMethod.Post, uri)
+ {
+ Content = content
+ };
+
+
+ var response = await client.SendAsync(request);
+ response.EnsureSuccessStatusCode();
+ }
+ }
+
+ ///
+ /// curl -i -X POST localhost:12110/datastores/boundaries/sparql -H "Accept: application/x.sparql-results+turtle-abbrev" -d "query=SELECT ?S ?P ?O WHERE { ?S ?P ?O }"
+ ///
+ ///
+ ///
+ ///
+ public static async Task QuerySparql(ConnectionSettings conn, string query)
+ {
+ using (var client = new HttpClient())
+ {
+ var uri = new Uri($"http://{conn.Host}:{conn.Port}/datastores/{conn.Datastore}/sparql");
+ var content = new StringContent(query, Encoding.UTF8, "application/sparql-query");
+
+ var request = new HttpRequestMessage(HttpMethod.Post, uri)
+ {
+ Content = content
+ };
+
+ var response = await client.SendAsync(request);
+ if(!response.IsSuccessStatusCode)
+ throw new Exception(await response.Content.ReadAsStringAsync());
+ return await response.Content.ReadAsStringAsync();
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/client/Boundaries/README.md b/client/Boundaries/README.md
new file mode 100644
index 00000000..4ad09ff0
--- /dev/null
+++ b/client/Boundaries/README.md
@@ -0,0 +1,11 @@
+# Boundaries to Commissioning app
+
+## Usage
+
+Start a rdfox server from the folder [../../rdfox/](../../rdfox) with the following command:
+
+```
+RDFox sandbox ../../rdfox boundaries
+```
+
+Then run `dotnet run` and follow instructions
diff --git a/client/Boundaries/TestBoundaries/DatalogCreatorTests.cs b/client/Boundaries/TestBoundaries/DatalogCreatorTests.cs
new file mode 100644
index 00000000..f6488cac
--- /dev/null
+++ b/client/Boundaries/TestBoundaries/DatalogCreatorTests.cs
@@ -0,0 +1,34 @@
+namespace TestBoundaries;
+
+public class DatalogCreatorTests
+{
+ [Fact]
+ public void TestCreateDatalog()
+ {
+ var datalogCreator = new Boundaries.DatalogCreator();
+ var graphIri = datalogCreator.BoundaryGraph;
+ var datalog = datalogCreator.CreateBoundaryDatalogRule("T4750", new IriTools.IriReference[]
+ {
+ new IriTools.IriReference("https://assetid.equinor.com/plantx#Nozzle-12"),
+ new IriTools.IriReference("https://assetid.equinor.com/plantx#Nozzle-8"),
+ new IriTools.IriReference("https://assetid.equinor.com/plantx#PlateHeatExchanger-1"),
+ new IriTools.IriReference("https://assetid.equinor.com/plantx#ReciprocatingPump-1")
+ });
+ Assert.Equal( $$"""
+
+ <{{graphIri}}> [?node] :-
+ rdfs:label [?internal, "T4750"],
+ imf:connectedTo [?internal, ?node],
+ dexpi:PipingOrEquipment [?node].
+
+ <{{graphIri}}> [?node] :-
+ <{{graphIri}}> [?node1],
+ imf:connectedTo [?node1, ?node],
+ NOT FILTER(?node1 = ),
+ NOT FILTER(?node1 = ),
+ NOT FILTER(?node1 = ),
+ NOT FILTER(?node1 = ),
+ dexpi:PipingOrEquipment [?node].
+ """ , datalog);
+ }
+}
\ No newline at end of file
diff --git a/client/Boundaries/TestBoundaries/GlobalUsings.cs b/client/Boundaries/TestBoundaries/GlobalUsings.cs
new file mode 100644
index 00000000..8c927eb7
--- /dev/null
+++ b/client/Boundaries/TestBoundaries/GlobalUsings.cs
@@ -0,0 +1 @@
+global using Xunit;
\ No newline at end of file
diff --git a/client/Boundaries/TestBoundaries/TestBoundaries.csproj b/client/Boundaries/TestBoundaries/TestBoundaries.csproj
new file mode 100644
index 00000000..b205ab91
--- /dev/null
+++ b/client/Boundaries/TestBoundaries/TestBoundaries.csproj
@@ -0,0 +1,29 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+ false
+ true
+
+
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+
+
+
+
+
+
diff --git a/datalog/boundary.datalog b/datalog/boundary.datalog
index 80063f75..52a7a6f7 100644
--- a/datalog/boundary.datalog
+++ b/datalog/boundary.datalog
@@ -1,5 +1,3 @@
-prefix dexpi:
-prefix imf:
prefix data:
diff --git a/owl/dexpi.ttl b/owl/dexpi.ttl
index 5875b92a..ae046d47 100644
--- a/owl/dexpi.ttl
+++ b/owl/dexpi.ttl
@@ -32,5 +32,7 @@ dexpi:Equipment rdf:type owl:Class ;
rdfs:subClassOf dexpi:PipingOrEquipment .
dexpi:Nozzle rdf:type owl:Class ;
rdfs:subClassOf dexpi:PipingOrEquipment .
+dexpi:PipingNetworkSegment rdf:type owl:Class ;
+ rdfs:subClassOf dexpi:PipingOrEquipment .
### Generated by the OWL API (version 4.5.26.2023-07-17T20:34:13Z) https://github.com/owlcs/owlapi
diff --git a/rdfox/README.md b/rdfox/README.md
index 4b2a2f18..03c4cb5d 100644
--- a/rdfox/README.md
+++ b/rdfox/README.md
@@ -5,7 +5,7 @@ Run the rml mapper scripts as described in [../rml/README.md](../rml/README.md)
This will create a file [../rml/pandid.trig](../rml/pandid.trig)
-## Running
+## Running the example
In this folder, run
```
RDFox sandbox . dexpi
@@ -24,3 +24,11 @@ and enter f.ex. the query
or
`select * where {?s a data:insideBoundaryX}`
Try also the `Explain` button on the web console.
+
+
+## Running just a server for the boundaries app
+To run a server for use with [Boundaries cli](../client/Boundaries/Boundaries.sln), run
+```
+RDFox sandbox . boundaries
+```
+This will load ontology and prefixes, but no data.
diff --git a/rdfox/boundaries.rdfox b/rdfox/boundaries.rdfox
new file mode 100644
index 00000000..d2459680
--- /dev/null
+++ b/rdfox/boundaries.rdfox
@@ -0,0 +1,24 @@
+dstore create boundaries
+active boundaries
+
+prefix rml:
+prefix rr:
+prefix ql:
+prefix rdf:
+prefix schema:
+prefix commonlib:
+prefix dexpi:
+prefix asset:
+prefix dbo:
+prefix dexpi:
+prefix imf:
+prefix data:
+
+
+import > asset:axioms ../owl/dexpi.ttl
+import > asset:axioms ../owl/imf-ontology.owl.ttl
+importaxioms asset:axioms
+import > schema:shacl ../shacl/imf-dexpi.shacl.ttl
+commitproc set commit-procedure.sparql
+
+endpoint start
diff --git a/rdfox/dexpi.rdfox b/rdfox/dexpi.rdfox
index c552ace2..9e100c89 100644
--- a/rdfox/dexpi.rdfox
+++ b/rdfox/dexpi.rdfox
@@ -1,30 +1,6 @@
-dstore create test
-active test
+boundaries.rdfox
-prefix rml:
-prefix rr:
-prefix ql:
-prefix rdf:
-prefix schema:
-prefix commonlib:
-prefix dexpi:
-prefix asset:
-prefix dbo:
-prefix dexpi:
-prefix imf:
-prefix data:
-
-
-tupletable delete connectedWithout force
-tupletable create connectedWithout type triple-table
import ../datalog/boundary.datalog
-import > asset:axioms ../owl/dexpi.ttl
-import > asset:axioms ../owl/imf-ontology.owl.ttl
-importaxioms asset:axioms
-import > schema:shacl ../shacl/imf-depi.shacl.ttl
-commitproc set commit-procedure.sparql
-
-
import +p "../rml/pandid.trig"
endpoint start
diff --git a/rml/piping-network-segment-map.rml.ttl b/rml/piping-network-segment-map.rml.ttl
index b9c3266d..ecaeea08 100644
--- a/rml/piping-network-segment-map.rml.ttl
+++ b/rml/piping-network-segment-map.rml.ttl
@@ -23,20 +23,22 @@
rr:class "https://rdf.equinor.com/dexpi#PipingNetworkSegment", "http://ns.imfid.org/imf#Connector, http://ns.imfid.org/imf#Block";
].
-# :PipingNetworkSegmentMap rr:predicateObjectMap [
-# rr:predicate imf:connectedTo;
-# rr:objectMap [
-# rr:template "https://assetid.equinor.com/plantx#{Connection/@FromID}";
-# rr:termType rr:IRI
-# ];
-# ],
-# [
-# rr:predicate imf:connectedTo;
-# rr:objectMap [
-# rr:template "https://assetid.equinor.com/plantx#{Connection/@ToID}";
-# rr:termType rr:IRI
-# ];
-# ] .
+:PipingNetworkSegmentMap rr:predicateObjectMap [
+ rr:predicate imf:connectedTo;
+ rr:objectMap [
+ rr:template "https://assetid.equinor.com/plantx#{Connection/@FromID}";
+ rr:termType rr:IRI
+ ];
+ rr:predicate imf:connectedTo;
+ rr:objectMap [
+ rr:template "https://assetid.equinor.com/plantx#{Connection/@ToID}";
+ rr:termType rr:IRI
+ ];
+ rr:predicate rdfs:label;
+ rr:objectMap [
+ rml:reference "Label/Text/@String"
+ ]
+] .
# :PipingNetworkSegmentMap rr:predicateObjectMap [