Skip to main content

Data model

This page shows how the terms in the Glossary relate to each other, and how many of each thing can sit on each side of a relationship. That "how many" is called cardinality.

Entity names in the diagrams are written in PascalCase (SpaceType), the same way they appear as TypeScript types. They mean exactly the glossary term (SpaceType = Space Type).

How to read the diagrams​

Each line connects two entities. The symbol at each end of the line says how many of the entity at that end take part.

SymbolMeaningRead it as
||exactly one"one and only one"
o| / |ozero or one"optional, at most one"
|{ / }|one or more"at least one"
o{ / }ozero or more"any number, maybe none"

Example: Site ||--o{ Space : contains reads in both directions:

  • One Site contains zero or more Spaces.
  • Each Space belongs to exactly one Site.

Where Mermaid can't express a rule (for example "exactly one of the two"), the rule is written under the diagram.

The whole model​

The full picture, without Device Status. Each section below zooms into one part.

Where things are​

The location hierarchy: Site → (Floor) → Space.

  • One Account owns zero or more Sites. Each Site is owned by exactly one Account.
  • The owner can share a Site with zero or more other Accounts, and an Account can have zero or more Sites shared with it. Shared Accounts can only view the Site.
  • One Site contains zero or more Spaces. Each Space belongs to exactly one Site.
  • Floors are optional. A Site has zero or more Floors, and a Space sits on zero or one Floor.
  • Every Space has exactly one Space Type (use Other when nothing fits). One Space Type is used by many Spaces.

How is tracked​

Devices (Hub and Module), the Sensors on Modules, and the Assets that Sensors watch.

  • A Space is home to zero or more Hubs. A Hub is in zero or one Space: it has no Space until it is Paired and placed.
  • A Hub has zero or more Modules wired to it. Each Module belongs to exactly one Hub: the one whose settings list it.
  • A Module has no Space of its own. Its Space is always its Hub's Space.
  • Every Module has exactly one Module Type (a category such as Climate). Which Sensors it contains (at least one) depends on its board.
  • A Sensor belongs to exactly one Module.
  • A Space contains zero or more Assets. Each Asset is in exactly one Space and has exactly one Asset Type.
  • An Asset is watched by one or more Sensors. A Sensor watches zero or one Asset: zero means it measures the Space itself (room air). When the last Sensor watching an Asset is removed, the Asset is removed too.

Device is the parent of Hub and Module​

Entity diagrams can't show "is a kind of", so this part uses a class diagram. The hollow arrow means "is a kind of".

  • A Sensor is not a Device. It has no Device Status and is never paired on its own.
  • An Asset is not a Device. It isn't connected and has no Device Status.
  • The filled diamond (*--) means "is made of": when a Module is removed, its Sensors go with it.
  • The hollow diamond (o--) means "has", but the parts live on their own: a Module wired to a Hub is still a Device of its own, with its own Device Status.

What is tracked​

Metrics, Units and the Readings Sensors produce.

  • A Sensor measures exactly one Metric. One Metric can be measured by many Sensors.
  • Each Metric has exactly one canonical Unit in the API. A Unit can be canonical for several Metrics (for example µg/m³ for PM2.5 and PM10) or for none (a display-only Unit like °F).
  • A Sensor produces zero or more Readings. Each Reading comes from exactly one Sensor and records exactly one Metric.
  • Each Reading has exactly one Subject: either a Space or an Asset. The diagram shows both links as optional; the rule "exactly one of the two" is in Rules the diagrams can't show.

Device Status​

Device Status is three separate fields. The cardinality shows the key rule: a Device always has a Pairing Status, but has a Connection Status only when Paired.

  • Pairing Status: exactly one, always.
  • Connection Status: zero or one. It exists only when Pairing Status is Paired.
  • Fault: zero or more ("None, or one or more Faults"), and only when Pairing Status is Paired. A Device can report several Faults at once, for example two broken Sensors. Faults are independent of Connection Status, so a Device can be Connected and have a Fault.
  • Each Fault has exactly one Fault Code.

Cardinality summary​

FromRelationshipToFrom sideTo side
AccountownsSite10..*
Siteis shared withAccount0..*0..*
SitecontainsSpace10..*
Siteis split intoFloor10..*
FloorgroupsSpace0..10..*
Space TypecategorisesSpace10..*
Spaceis home toHub0..10..*
Hubhas wired to itModule10..*
Module TypedefinesModule10..*
ModulecontainsSensor11..*
SpacecontainsAsset10..*
Asset TypecategorisesAsset10..*
Assetis watched bySensor0..11..*
Metricis measured bySensor10..*
Unitis canonical forMetric10..*
SensorproducesReading10..*
Metricis recorded inReading10..*
Subject (Space or Asset)is the Subject ofReading10..*
DevicehasPairing Status11
DevicehasConnection Status10..1 (only when Paired)
Devicemay reportFault10..* (only when Paired)
Faultidentified byFault Code11

Rules the diagrams can't show​

These are invariants: rules that must always be true. The code (types, API validation, backend) has to enforce them.

  1. A Module belongs to exactly one Hub: the Hub whose settings list it.
  2. A Module's Space is its Hub's Space. A Module never stores its own Space.
  3. A Sensor's Metric is fixed by its board. The user can't change it.
  4. A Sensor can only watch an Asset in its own Space. The Asset's Space must equal the Space of the Sensor's Hub.
  5. A Reading has exactly one Subject. If its Sensor watches an Asset, the Subject is that Asset; otherwise it is the Sensor's Space.
  6. A Reading never changes once recorded. Its Sensor, Metric, value, Unit and time are fixed.
  7. A Reading is stored in its Metric's canonical Unit. Other Units are only for display.
  8. Connection Status exists only when Pairing Status is Paired.
  9. A Floor and its Spaces belong to the same Site.
  10. A Hub gets a Space only after it is Paired. An Unpaired or Pairing Hub has no Space.
  11. An Asset always has at least one Sensor. When the last Sensor watching an Asset is removed (for example, its Module is removed), the Asset is removed too, and so are all Readings about it.
  12. A Module can move to another Hub, but it starts again. It pairs again on the new Hub, and its past Readings are deleted. See Device lifecycle.
  13. A Site has exactly one owner. The Accounts it is shared with never include its owner.
  14. Sharing is view-only. Accounts a Site is shared with can see it and its Readings, but can't change anything or share it further. Only the owner makes changes.

Aggregates​

In Domain-Driven Design an aggregate is a group of things that are always changed together, through one "root" entity. Other parts of the system point at the root by its ID and never reach inside.

AggregateRootContainsPoints to (by ID)
SiteSiteFloors, Spaces, AssetsAccount, Space Type, Asset Type
HubHubModules, Sensors, Device StatusSpace, Module Type, Metric, Asset
ReadingReading(nothing; it's a single fact)Sensor, Metric, Subject

Reference data never changes during normal use and is shared by everyone: Space Type, Asset Type, Module Type, Metric, Unit, Device Kind, Fault Code.

Why Readings are their own aggregate: there are millions of them and they never change, so they shouldn't be loaded every time someone renames a Hub.