Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add datatype constraints to lastKnownValue #433

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

gtfierro
Copy link
Member

Add "preferredDatatype" annotations to Quantities in bricksrc/quantities.py:

quantity_definitions = {
    "Air_Quality": {
        "preferredDatatype": XSD.float,    # <---- like this
        SKOS.narrower: {
            "Ammonia_Concentration": {
                QUDT.applicableUnit: [UNIT.PPM, UNIT.PPB],

This will create subshapes of bsh:LastKnownValue which have the datatype constraints:

bsh:LastKnownTemperatureValueShape a owl:Class,
        sh:NodeShape ;
    rdfs:subClassOf bsh:LastKnownValueShape,
        bsh:ValueShape ;
    sh:node bsh:TemperatureShape ;
    sh:property [ a sh:PropertyShape ;
            sh:datatype xsd:dateTime ;
            sh:minCount 1 ;
            sh:path brick:timestamp ] .


bsh:TemperatureShape a owl:Class,
        sh:NodeShape ;
    rdfs:subClassOf bsh:ValueShape ;
    sh:property [ a sh:PropertyShape ;
            sh:in ( unit:DEG_F unit:PlanckTemperature unit:DEG_R unit:DEG_C unit:MilliDEG_C unit:K ) ;
            sh:maxCount 1 ;
            sh:minCount 1 ;
            sh:path brick:hasUnit ],
        [ a sh:PropertyShape ;
            sh:datatype xsd:float ;
            sh:maxCount 1 ;
            sh:minCount 1 ;
            sh:path brick:value ] .

and then these will be added as optional properties on relevant Point types:

brick:Temperature_Sensor sh:property [ 
            sh:maxCount 1 ;
            sh:node bsh:LastKnownTemperatureValueShape ;
            sh:path brick:lastKnownValue ] ;

@gtfierro
Copy link
Member Author

@hammar does this look like it will address your needs?

@hammar
Copy link
Contributor

hammar commented Oct 3, 2022

Thanks for your work on this! I'm only looking at the generated SHACL, not the Python source (yet).

I have a couple of concerns:

  1. This causes lastKnownValue shapes to be created for every point type that has a QUDT quantity, which is a little too ambitious for our needs. If those shapes are declared and linked on every level of the point hierarchy we'll end up with subclasses that redefine their parents' interpretation of lastKnownValue; this isn't allowed when translating to DTDL. What we'd need is really more modest: basic coupling to expected XSD type one step below Sensor should be enough. E.g., "TemperatureSensor" (and by inference it's subclasses) will typically report floating point values, "MotionSensor" probably report boolean values, etc.
  2. Conversely, for those Points that do not have QUDT mappings (e.g., MotionSensor), no such mappings are created per this approach.

The goal for us isn't necessarily to prescribe a schema for every possible type of point, but to give something one step more fine-grained than every lastKnownValue being a string. I hope I'm not complicating your work too much. If so I could look into implementing this as some sort of shim on our end instead.

Best,

Karl

@hammar
Copy link
Contributor

hammar commented Oct 3, 2022

Below is an excerpt from my preliminary shim that achieves what I'm looking for. As mentioned, if this isn't in line with your own preferences, no worries -- we can do it on our end in our brickpatches.ttl, full of hacks that we graft on. :-)

brick:Temperature_Sensor
  sh:property [
      rdf:type sh:PropertyShape ;
      sh:path brick:lastKnownValue ;
      sh:class <https://brickschema.org/schema/BrickShape#LastKnownDoubleShape> ;
      sh:maxCount 1 ;
      sh:name "last known value" ;
      sh:nodeKind sh:IRI ;
    ] ;
.
<https://brickschema.org/schema/BrickShape#LastKnownDoubleShape>
  rdf:type owl:Class ;
  rdf:type sh:NodeShape ;
  rdfs:subClassOf <https://brickschema.org/schema/BrickShape#LastKnownValueShape> ;
  sh:property [
      rdf:type sh:PropertyShape ;
      sh:path brick:timestamp ;
      sh:datatype xsd:dateTime ;
      sh:maxCount 1 ;
      sh:minCount 1 ;
      sh:name "timestamp" ;
    ] ;
  sh:property [
      rdf:type sh:PropertyShape ;
      sh:path brick:value ;
      sh:datatype xsd:double ;
      sh:maxCount 1 ;
      sh:minCount 1 ;
      sh:name "value" ;
    ] ;
.

@hammar
Copy link
Contributor

hammar commented Oct 19, 2022

Hi again,

So, it turns out user/customer demands vary more on this than I initially understood. I'm spending a bit of time thinking on how to bend SHACL/RDF + DTDL to my will, but I don't have any great solutions yet. So this is just to say that my previous posts with suggested solutions are not complete from a REC point-of-view and I'd be happy to keep this discussion alive once I've understood a bit more about the options. Will get back to you.

Karl

@gtfierro
Copy link
Member Author

@hammar let me know if you'd like to pick this conversation up again!

@hammar
Copy link
Contributor

hammar commented Nov 29, 2022

It turns out this touches also upon the REC need to expose observations/readings on points as events, in order to generate API endpoints for ingesting/emitting such readings. The schema for such events is a natural candidate for lastKnownValue, as they are essentially (typed) values/timestamps.

Here's what we're leaning towards in REC 4 GA (grafting on some statements on Brick sensors and/or other Points):

brick:Angle_Sensor
  sh:property [
      rdf:type sh:PropertyShape ;
      sh:path brick:lastKnownValue ;
      sh:class rec:AngleObservation ;
      sh:maxCount 1 ;
      sh:name "last known value" ;
      sh:nodeKind sh:IRI ;
    ] ;
.

This is combined with:

rec:AngleObservation
  rdf:type rdfs:Class ;
  rdf:type sh:NodeShape ;
  qudt:hasQuantityKind <http://qudt.org/vocab/quantitykind/Angle> ;
  rdfs:label "Angle observation" ;
  rdfs:subClassOf rec:ObservationEvent ;
  sh:property [
      rdf:type sh:PropertyShape ;
      sh:path rec:value ;
      sh:datatype xsd:double ;
      sh:maxCount 1 ;
      sh:minCount 1 ;
      sh:name "value" ;
    ] ;

We're presently only grafting the first PropertyShape above onto a reasonable selection of Point classes, due to the aforementioned problem of DTDL translation not allowing field overloading. We're targeting those Points where it is reasonable to assume that all subclasses will have the same representation of lastKnownValue, i.e., we're placing it as low as possible, but no lower. For the current work in progress on which points that are covered, see RealEstateCore/rec#204.

If Brick wants to copy part of this design upstream, you're more than welcome of course. But I realize this may be bending a bit too much toward the design criteria of DTDL rather than SHACL, so if you prefer not to go this way, we can maintain it in our set of patches to Brick instead.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants