Standard / BUS-1 v0.3 / Annex G

G Mapping to Project Haystack and Brick§

Informative, except that the rules stated for the classifications field, for ports on import, and for the medium of a flow relationship are normative. The machine-readable forms are published as bus-haystack-map.json and bus-brick-map.json, and every count in this annex is computed from them. Where a rule here and a rule there differ, the file governs: it is generated from the ontologies and this text is not.

G.1 What this annex is and is not§

This annex maps no taxonomy onto any other. This document defines no equipment taxonomy (5.3.3) and preserves external classifications verbatim (7.7), so a Haystack def and a Brick class reach a package as a classification and never as a type. Nothing here reduces Brick’s 1,433 classes to this model’s 26, because that is not the shape of the problem.

The problem it solves is narrower and more consequential. 7.7 requires classifications to be preserved verbatim and does not say which classifications. Two implementers exporting the same Haystack model can both obey it and produce different packages: one emits a tag per marker, another emits the composed name it displays in its own interface, a third emits the equipment def and drops the rest. Verbatim needs a referent. This annex supplies one.

Project HaystackBrick Schema
Version this annex is written against4.0.01.4.4
classification.systemhaystackbrick, brick-tag, rec
classification.version4.0.01.4.4, and omitted on a rec: code
Vocabulary size719 defs, of which 302 markers and 162 declared conjuncts1,433 classes, of which 185 deprecated
Entity types the crosswalk places142, of which 7 reach no BUS type56 branches covering 1,431 classes
Predicates mapped33, of which 5 reach no BUS predicate37, of which 23 reach no BUS predicate
Ports recoverable280 — the model has none (9.4)
Constructs that reach BUS in no form119
BUS constructs the source cannot express1012

G.2 Classifications from Project Haystack§

An exporter from Project Haystack MUST use system: "haystack" and version: "4.0.0". The code is the def symbol exactly as authored, without the leading caret: ahu, chilled-water-plant, discharge. Not the library-qualified form, not the URI, not a display name.

Haystack’s meaning lives in a tag set rather than in a name, and that is what makes the rule necessary. There is no def for a discharge air temperature sensor. The four applied tags each name a def, and the largest declared conjunct they match is air-temp, which covers two of them. An exporter that synthesizes a code for the composition invents a term the source vocabulary does not contain, which is exactly what Requirement 7.7-1 forbids.

  • R1. An exporter MUST emit one classification per applied marker tag that names a def. Tags that name no def are not classifications; they are property values (7.8) or named fields.
  • R2. An exporter MUST additionally emit one classification for the maximal matched conjunct, where one exists: the declared conjunct whose parts are all present in the applied tag set and which has the most parts. Where two conjuncts tie at the maximum, all tied conjuncts MUST be emitted, because the match is genuinely ambiguous and picking one silently invents a fact.
  • R3. The maximal-conjunct match MUST exclude the port conjuncts. A port is an interface assertion, not a class: emitting 'air-input' as a classification asserts that an air handler is a kind of air inlet. Ports go to Asset.ports via portMap.
  • R4. An exporter MUST NOT synthesize a code that is not a def symbol. There is no def for a composed point such as 'discharge air temp sensor' — the maximal match for those four tags is a two-part conjunct — and inventing one violates 7.7-1's verbatim requirement.
  • R5. Classifications MUST NOT be deduplicated against each other or against the entity's type. An entity may carry the marker code and the conjunct code that contains it; 7.7 permits several from one system and does not adjudicate between them.
  • R6. The choice-slot options a point carries (pointFunction, ductSection, and the other choice slots) are marker tags and are emitted under R1 like any other. BUS does not model the slot, only the option.
Applied tagsMaximal matched conjunctClassifications emitted
discharge air temp sensorair-tempair, air-temp, discharge, sensor, temp
chilled water plantchilled-water-plantchilled, chilled-water-plant, plant, water
elec meterelec-meterelec, elec-meter, meter
air input(none)air, input

NOTE Haystack's authored form is not its normalized form. contains, tags and quantities are computedFromReciprocal and never appear in .trio source; inherited tags are derivable but not materialized. An exporter reading a Haystack server through op:defs therefore sees a larger model than one reading source. A conformance claim MUST state which was consumed.

G.3 Classifications from Brick§

An exporter from Brick MUST use system: "brick" and version: "1.4.4", and the code is the CURIE exactly as Brick spells it: brick:Chilled_Water_System, rec:Room, tag:Discharge. The prefix is part of the code, because one crosswalk emits 3 namespaces.

The rule that matters is deprecation. 185 of Brick’s 1,433 classes are deprecated, 184 of them carrying a replacement, 112 of which point into the rec: namespace. The whole of the Location branch — 109 classes — is deprecated in favor of rec:, which is to say that Brick has delegated its spatial vocabulary to another ontology mid-life.

  • R1. An exporter MUST emit one classification per asserted rdf:type in the brick: namespace, with system 'brick' and version '1.4.4'.
  • R2. Where the class is deprecated AND carries brick:isReplacedBy, the exporter MUST emit the replacement as a second classification, with system 'rec' where the replacement is in the rec: namespace and 'brick' where it is in brick:. It MUST NOT emit the replacement instead of the original: the original is what the source graph actually said, and 7.7-1 forbids translating.
  • R3. classification.version MUST be omitted on a rec: code. The shipped Brick distribution states no REC version, and inventing one is worse than omitting it.
  • R4. Superclasses MUST NOT be emitted as additional classifications. The asserted type is the assertion; the ancestry belongs in abstractTypes (8.2), which is BUS's mechanism for exactly this and is not a classification.
  • R5. Tags asserted with brick:hasTag are emitted with system 'brick-tag'. Tags reachable only through the class, via brick:hasAssociatedTag, MUST NOT be emitted: they are ontology content, not instance data, and emitting them would put 1249 classes' worth of derived tags into packages that never asserted them.
  • R6. An importer MUST NOT deduplicate the brick: code against the rec: code. They are different vocabularies making the same claim, and a receiver may recognize either.

Emitting both codes is not belt and braces. In the shipped distribution the 268 rec: classes are typed rdfs:Class and sh:NodeShape, not owl:Class. A tool that discovers Brick’s hierarchy by asking for owl:Class — which is precisely how the 1,433 figure above is produced — sees 109 deprecated location classes and none of their replacements. A crosswalk targeting rec: alone is therefore invisible to a conventional Brick consumer, and one targeting brick: alone hands the receiver a vocabulary its own publisher has abandoned. 7.7 permits an entity to carry several classifications from one system and does not adjudicate between them, so both travel and the receiver decides.

NOTE This is the clearest vindication of 5.3.3 in the whole exercise. Because this model never adopted a spatial taxonomy, Brick’s abandonment of its own costs it nothing. A standard that had adopted one would be republishing.

G.4 Ports on import§

A Haystack equipment def declares its ports in its own is list, as conjuncts of the form substance-input and substance-output. An importer MUST partition that list into genus and ports before emitting anything. It MUST emit each port conjunct as an entry in Asset.ports, with name equal to the conjunct symbol so that two independent importers produce the same name, and it MUST NOT emit a port conjunct as a classification or as an abstractType (8.2).

The hazard is concrete and easy to walk into. 34 defs declare a port in their is list, and all 34 of them owe their multiple inheritance entirely to it. A translator that turns every is edge into a classification asserts that an air handler is a kind of air outlet, which is not a statement Haystack makes and not one a receiver can undo.

Haystack’s encoding cannot supply ordinal or pairedWith, because a port is a supertype there and a def can carry at most one port per medium per direction. An importer MUST leave both absent. Synthesizing an ordinal invents topology. This is the one place where v0.3 is strictly more expressive than the source and the crosswalk cannot fill the gap; it can only decline to fabricate.

NOTE The port prior art in the Brick distribution is extensions/223p.ttl, which is ASHRAE 223P and is not in Brick’s own imports. A model carrying 223P alongside Brick can populate Asset.ports; a plain Brick model cannot. 9.4 credits 223P for the shape.

G.5 The medium of a flow relationship§

Brick puts substance on the node rather than on the edge: hasInputSubstance and hasOutputSubstance have SHACL domain brick:Equipment. Requirement 9.3 requires a medium on every bus:feeds. A chiller with chilled water out one side and condenser water out the other therefore has two feeds edges in the source and no statement anywhere of which carries which.

This is the only route by which a conforming import from Brick could otherwise produce an invalid package, and closing it needs no schema change: qualifiers is already an open map and medium already has other. 14 of Brick’s 60 substances reach this model only as other in any case, among them glycol, oil, ice and several gases, and the source term in qualifiers is what a later reader has to work with.

Brick also draws positional distinctions inside the substance — supply air, return air, entering and leaving chilled water. Here the position is a property of the port and not of the medium (9.4), so those widen onto the base medium and survive in the classification. A source that carried port structure would let a receiver recover the position; Brick does not carry one.

G.6 Identity, provenance, and what does not cross§

Two obligations catch every import from either model, because neither model has the construct this document requires.

  • Identity — a Haystack ref and a Brick IRI are not BUS identifiers under 6.1. An importer mints a BUS identifier and carries the source identifier in externalIds with its scheme (6.3). Discarding it is the one thing that makes a second import impossible to reconcile with the first.
  • Provenance — neither model carries provenance on an assertion, and method is REQUIRED (14.1). An importer MUST assert method: "imported", naming itself as the agent and the source model as the source, rather than leaving provenance absent. It is a thin claim and it is an honest one: the receiver can see that nothing behind this assertion was surveyed.

Beyond that, 10 constructs of this model cannot be expressed in Haystack and 12 cannot be expressed in Brick, and the two lists are largely the same list: operational intent, sequences, alarm definitions, events, knowledge notes, the presentation model of clause 13, provenance on every assertion, and the completeness declaration itself. An export from this model into either MUST declare the affected domains partial with reason notSupportedByExporter.

NOTE That is not a criticism of either model. Both are semantic models of what a building is; this is a portability standard for what an owner is owed when they leave. The overlap is what makes a crosswalk possible and the non-overlap is the reason this document exists.

Take my building with me.

Not a data dump. Not a proprietary backup. A portable, durable, reconstructable understanding of the building the owner already paid to create.

END OF BUS-1 · VERSION 0.3 · WORKING DRAFT