Package: brick-bodge
Rule-Based Model BuilderThis repository implements an experimental rule-based approach to creating Brick models (or really any RDF graph) from variably-structured metadata sources.
The APIs and interfaces in this repository may change drastically with little notice, and documentation is sparse! That said, this is fast emerging as my favorite way for creating Brick models from arbitrary sources.The key idea is to write small functions that generate part of a Brick graph when certain conditions are met on the input data. The input data is structured as a sequence of key-value documents, calledrows. It is usually straightforward to turn some source data into a sequence of key-value documents, so this is a natural "intermediate representation" (IR) for arbitrary metadata sources.The framework defined in this repository provides a number of simple "rule conditions" for triggering these functions which produce parts of the Brick graph. These rule conditions are designed to make it easier to deal with heterogeneously-structured input data.Rule ConditionsEach condition is implemented as a Python decorator. Decorated functions should take a single argument which is the "row" (a Pythondict)  that matches the conditions defined by the decoratortags: triggers the rule when all of the keys are present@tags("AHU")defadd_ahu(row):ahu_name=row.get("AHU")G.add((BLDG[ahu_name],A,BRICK.AHU))G.add((BLDG[ahu_name],RDFS.label,Literal(row.get("AHU"))))oneof: triggers the rule when at least one of the provided keys is presentpoint_types={"alarm variable":BRICK.Alarm,"air volume":BRICK.Supply_Air_Flow_Sensor,"differential pressure status sensor":BRICK.Differential_Pressure_Sensor,"discharge air pressure sensor":BRICK.Discharge_Air_Static_Pressure_Sensor,"fan speed reset":BRICK.Speed_Reset_Command,"zone temp":BRICK.Zone_Air_Temperature_Sensor,"zone temp setpoint":BRICK.Zone_Air_Temperature_Setpoint,}@oneof(*point_types.keys())defdefine_point(row):fortag,brickclassinpoint_types.items():iftaginrow:sensor=row.get(tag)G.add((BLDG[sensor],A,brickclass))values: triggers the rule when the row has the given key-value pairs@values({"Category":"Level","ModelObject":"IfcBuildingStorey"})defadd_floors(row):floor_id=EX[row["ModelID"]]G.add((floor_id,RDFS.label,Literal(row["Name"])))G.add((floor_id,A,BRICK.Floor))G.add((floor_id,BRICK.elevation,[(BRICK.value,Literal(float(row["Elevation"]))),(BRICK.unit,units["linear"]),]))_and_: triggers the rule when all of the given conditions are met. Sorry for the funky syntax; ideally the decorators compose easily but this will take some work to figure out how to do@_and_([tags,"AHU","uuid"],[oneof,*point_types.keys()])defahu_points(row):ahu_name=row.get('AHU')forcls,brickclassinpoint_types.items():ifclsinrow:point=row.get(cls)G.add((BLDG[point],A,brickclass))G.add((BLDG[point],BRICK.timeseries,[(BRICK.hasTimeseriesId,Literal(row.get("uuid")))]))G.add((BLDG[ahu_name],BRICK.hasPoint,BLDG[point]))fixedpoint: executes the rule if any new triples were added to the graph since it was last executed. This is helpful to avoid any dependency constraints between rules (e.g. making sure the floors exist so the biulding can be linked to them). The argument to thefixedpointdecorator is the string name of the graph object you are appending to (should beGin most cases)The triples generated by this rule should beconsistentso that RDFlib can effectively deduplicate. Generating a blank node with a random identifier can lead this rule to execute forever. Use with caution! This should always be the decorator immediately above the function definition.@tags("AHU")@fixedpoint("G")defahu_feeds(row):ahu=row.get("AHU")idnum=ahu[-1]vavs=G.subjects(predicate=A,object=BRICK.VAV)forvavinvavs:label=list(G.objects(subject=vav,predicate=RDFS.label))[0]iflabel.startswith(idnum):G.add((BLDG[ahu],BRICK.feeds,vav))DrivingThe stream of documents are done through aStreamobject. AStreamobject implements an optionalsetupmethod and anextmethod. ACSVFileStreamandJSONFileStreamimplementation are provided. Some files (e.g.examples/smc/smc.pywill define their own extensions to these streams to handle tasks like column renaming, data cleaning and transformations.Call thedrivefunction on 1 or moreStreamobjects to create the graph (by default, stored in a globalGvariable), then optionally validate (G.validate()) and serialize.