Writing shapes¶
In this tutorial you write shapes for the VAV boxes in a building, validate
a small building against them, and fix the one failure. You need the
shifty command (see Install Shifty).
Write the shapes¶
Save this as vav.shm:
-- Shapes for the VAV boxes of a building.
base <urn:example/tutorial/>
prefix brick: <https://brickschema.org/schema/Brick#>
doc "A VAV box: fed by one AHU, with an airflow sensor and a damper."
shape vav for every brick:VAV {
fed: brick:isFedBy exactly 1, each a brick:AHU
flow: brick:hasPoint at least 1 is a brick:Air_Flow_Sensor
damper: brick:hasPart at least 1 matches damper
}
doc "A damper that can be commanded to a position."
shape damper {
is a brick:Damper
brick:hasPoint at least 1 is a brick:Damper_Position_Command
}
Line by line:
basesets the IRI the shapes are named under.vavbecomes<urn:example/tutorial/vav>.prefixworks as in Turtle.--starts a comment.doc "..."documents the next shape. Unlike a comment, it is kept: it is exported asrdfs:commentand shown in reports.shape vav for every brick:VAVdefines a shape and says which nodes it checks.damperhas nofor, so it checks nothing by itself.vavuses it.Each line in braces is one requirement, and all must hold.
brick:isFedBy exactly 1, each a brick:AHUmeans onebrick:isFedByvalue, and it is abrick:AHU.at least 1 is a brick:Air_Flow_Sensorcounts only the points that are air-flow sensors. The box can have other points too.atests a class andmatchestests a shape, soat least 1 matches dampercounts parts that matchdamper.fed:,flow:, anddamper:name their lines. Names are optional. Reports and the exported SHACL use them.is a brick:Damperis a requirement on the node itself.
Check them¶
shifty shape check builds the shapes without any data, so you catch
mistakes before validating anything:
ok: vav.shm builds
Errors point at their line. typo.shm is vav.shm with exactly
misspelled:
error: unexpected `exactl` in a field line
--> typo.shm:7:22
7 | fed: brick:isFedBy exactl 1, each a brick:AHU
| ^
hint: for values that are `exactl`, write `each matches exactl`; to follow another property, write a path like `ex:p/exactl`
error: 1 error in typo.shm
Validate a building¶
This building has two VAV boxes. The damper of vav-2 has no position
command:
@prefix brick: <https://brickschema.org/schema/Brick#> .
@prefix ex: <urn:example/building/> .
ex:ahu-1 a brick:AHU .
ex:vav-1 a brick:VAV ;
brick:isFedBy ex:ahu-1 ;
brick:hasPoint ex:vav-1-flow ;
brick:hasPart ex:vav-1-damper .
ex:vav-1-flow a brick:Air_Flow_Sensor .
ex:vav-1-damper a brick:Damper ;
brick:hasPoint ex:vav-1-damper-cmd .
ex:vav-1-damper-cmd a brick:Damper_Position_Command .
# vav-2's damper has no position command.
ex:vav-2 a brick:VAV ;
brick:isFedBy ex:ahu-1 ;
brick:hasPoint ex:vav-2-flow ;
brick:hasPart ex:vav-2-damper .
ex:vav-2-flow a brick:Air_Flow_Sensor .
ex:vav-2-damper a brick:Damper .
<urn:example/building/vav-2> fails vav (vav.shm:6)
"A VAV box: fed by one AHU, with an airflow sensor and a damper."
damper: brick:hasPart at least 1 matches damper (vav.shm:9)
at least 1 value(s) matching `instance of brick:Damper and ∃[1..] brick:hasPoint . instance of brick:Damper_Position_Command` required along brick:hasPart, found 0
does not conform: 1 node failed
The report names the node, the shape, and the line that failed, and points
into vav.shm. It quotes the shape’s doc, and says what the line
needed and what it found. vav-1 conforms, so it isn’t listed.
The command exits with status 1, so a CI job fails on it.
To fix the building, give the damper its command:
ex:vav-2-damper a brick:Damper ;
brick:hasPoint ex:vav-2-damper-cmd .
ex:vav-2-damper-cmd a brick:Damper_Position_Command .
Export SHACL¶
Other SHACL engines don’t read .shm. shifty shape export writes the
shapes as SHACL Turtle:
@prefix : <urn:example/tutorial/> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix brick: <https://brickschema.org/schema/Brick#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix sh: <http://www.w3.org/ns/shacl#> .
@prefix shapeshift: <https://shifty.gtf.fyi/ns/shapeshift#> .
:
a owl:Ontology .
:vav
a sh:NodeShape ;
sh:property :vav.fed, :vav.flow, :vav.damper ;
rdfs:comment "A VAV box: fed by one AHU, with an airflow sensor and a damper." ;
sh:targetClass brick:VAV .
:vav.fed
a sh:PropertyShape ;
sh:path brick:isFedBy ;
sh:minCount 1 ;
sh:maxCount 1 ;
sh:class brick:AHU .
:vav.flow
a sh:PropertyShape ;
sh:path brick:hasPoint ;
sh:qualifiedValueShape [
sh:class brick:Air_Flow_Sensor
] ;
sh:qualifiedMinCount 1 .
:vav.damper
a sh:PropertyShape ;
sh:path brick:hasPart ;
sh:qualifiedValueShape :damper ;
sh:qualifiedMinCount 1 .
:damper
a sh:NodeShape ;
sh:property [
sh:path brick:hasPoint ;
sh:qualifiedValueShape [
sh:class brick:Damper_Position_Command
] ;
sh:qualifiedMinCount 1
] ;
sh:class brick:Damper ;
rdfs:comment "A damper that can be commanded to a position." .
Named lines become named property shapes (:vav.fed), so other shapes
and reports can refer to them. each a brick:AHU became sh:class, and
at least 1 is ... became a qualified value shape.
Format¶
shifty shape fmt prints a file in the standard layout, keeping comments
and docs.
fmt -wrewrites the file.fmt --checkexits with status 1 if the file isn’t formatted. Use it in CI.
The same shapes in Python¶
When shapes come from data, it is easier to generate them.
shifty.experimental.shapeshift builds the same module from Python:
from shifty.experimental import shapeshift as ss
from shifty.experimental.shapeshift import Shape
BRICK = ss.Namespace("brick", "https://brickschema.org/schema/Brick#")
m = ss.Module(
"vav",
base="urn:example/tutorial/",
comment="Shapes for the VAV boxes of a building.",
)
m.vav = Shape(
fed=BRICK.isFedBy.exactly(1).each(BRICK.AHU),
flow=BRICK.hasPoint.at_least(1, BRICK.Air_Flow_Sensor),
damper=BRICK.hasPart.at_least(1, m.damper),
for_every=BRICK.VAV,
doc="A VAV box: fed by one AHU, with an airflow sensor and a damper.",
)
m.damper = Shape(
BRICK.Damper,
BRICK.hasPoint.at_least(1, BRICK.Damper_Position_Command),
doc="A damper that can be commanded to a position.",
)
print(m, end="")
-- Shapes for the VAV boxes of a building.
base <urn:example/tutorial/>
prefix brick: <https://brickschema.org/schema/Brick#>
doc "A VAV box: fed by one AHU, with an airflow sensor and a damper."
shape vav for every brick:VAV {
fed: brick:isFedBy exactly 1, each a brick:AHU
flow: brick:hasPoint at least 1 is a brick:Air_Flow_Sensor
damper: brick:hasPart at least 1 matches damper
}
doc "A damper that can be commanded to a position."
shape damper {
is a brick:Damper
brick:hasPoint at least 1 is a brick:Damper_Position_Command
}
The output matches vav.shm line for line.
Next steps¶
The Shapeshift language: groups of named points, templates,
any ofandwhen, and imports.Shapeshift in Python: the Python module.