Defs bind a symbolic name to a formal definition. Defs are modeled as a dict with the def tag. Defs are used to define the tags we use on Haystack data. And because they are dicts themselves, defs are normal Haystack data also.
The primary tags used to model a def include:
See def tags for the full list of all tags which might be used on a definition.
Defs allow us to build up layers of our information model:
Lets start at the bottom: you can use the Haystack data model and file formats without any defs. Even without defs, Haystack provides a nice framework for structuring and exchanging data. It is like JSON but with a richer type system since we have first class types for dates, times, references, etc.
The real power of Haystack is unleashed when we use the def framework to formally define our tag names. As a first step we create a def for each of our tag names (or conjuncts for sets of tag names). This allows to us to precisely define our vocabulary. Each of those tags now can be reflected to its definition.
We use the is
tag in our defs to organize our meta-model into a taxonomy through subtyping. Subtyping allows us to relate terms as hyponyms and hypernyms. For example we define that water is a subtype of liquid. Subtyping allows us to infer semantics beyond the presence of tags.
Ontologies extend taxonomies to include the relationships between concepts. In Haystack we build out this ontological information using symbolic tags on defs to cross-reference other defs. For example, we can model that a duct conveys air:
def: ^duct conveys: ^air
Ontological relationships provide tremendous power to query the knowledge from Haystack data.
There are three types of definitions determined by the format of their symbol name:
hot-water
filetype:json
Collectively we call tags and conjuncts our terms.
Tag defs are used to formally define tag names. They must have symbol names which are valid tag names:
Here is an example:
def: ^equip doc: "Equipment asset" is: ^entity
Conjuncts are used to define a set of two or more tags used together in combination. Conjuncts are like the compound words used in English and other languages. We coin a new term from existing terms.
Conjunct symbols are formatted as individual names separated by a dash. Each tag name used in a conjunct must itself be formally defined and must be a subtype of marker.
Conjuncts are applied to dicts by applying each tag separately. For example to apply the elec-meter conjunct to a dict, we would add the individual tags elec and meter.
The order of the names in a conjunct is significant - the symbol itself defines a unique identifier. For example hot-water
is not the same identifier as water-hot
. Note this is in contrast to instance data modeled as dicts which do not define any ordering on their tags.
The general rule for ordering the tags in a conjunct is to put the most important term or noun at the end. For example since hot
is an adjective on the noun water
, then we order the conjunct as hot-water
. The order often maps to how we would use the phrase or compound word in normal language.
Conjuncts have no implied subtyping. For example hot-water does not imply that it subtypes from either hot or water. Subtyping must be explicitly specified in the def via the is
tag.
Here is an example:
def: ^hot-water is: ^water doc: "Hot water used for HVAC heating or supply to hot taps"
Any def which is either a tag or a conjunct is a term. Terms define our standardized vocabulary to model data. It can be difficult to determine when a term should be a camel case tag or a conjunct. For example should we use hotWater
as a single tag or hot-water
as a conjunct?
One consideration is how instance data will be queried with tags. By making a term a conjunct, we can easily query the individual tags. For example since hot-water
is a conjunct, we can query data for the water
tag which would include hot-water
, chilled-water
, etc. Although in Haystack 4.0 we can also perform these queries using the subtype tree.
Another consideration is semantic conflicts. Many of the primary entity tags carry very specific semantics. For example the site tag by its presence means the data models a geographic site. So we cannot reuse the site
tag to mean something associated with a site; which is why use the camel case tag siteMeter to mean the main meter associated with a site.
Feature keys create named definitions which are application specific. Feature key symbols are formatted as feature:name
. Currently we define two features:
Feature keys let us share a single unified symbolic namespace for all definitions. But they don't pollute our tag namespace. For example the symbol filetype:json lets us define the JSON file format, but its wholly separate if we ever decide we want a json
tag definition.
Feature keys require that the feature name be formally specified as a subtype of feature. The name of the key must be a valid tag name. All the names within a feature key must be unique.
Feature keys are implied to subtype from their feature. For example lib:phIoT automatically subtypes from lib. It is invalid to declare an is
tag on a feature key def.
Feature keys are singletons. They don't have data instances. For example, there is no combination of tags applied to dicts to implement the lib:ph
def. The lib:ph
def is itself the only instance.