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 [