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.
| Symbol | Meaning | Read it as |
|---|---|---|
|| | exactly one | "one and only one" |
o| / |o | zero or one | "optional, at most one" |
|{ / }| | one or more | "at least one" |
o{ / }o | zero 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
Otherwhen 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 beConnectedand have a Fault. - Each Fault has exactly one Fault Code.
Cardinality summary
| From | Relationship | To | From side | To side |
|---|---|---|---|---|
| Account | owns | Site | 1 | 0..* |
| Site | is shared with | Account | 0..* | 0..* |
| Site | contains | Space | 1 | 0..* |
| Site | is split into | Floor | 1 | 0..* |
| Floor | groups | Space | 0..1 | 0..* |
| Space Type | categorises | Space | 1 | 0..* |
| Space | is home to | Hub | 0..1 | 0..* |
| Hub | has wired to it | Module | 1 | 0..* |
| Module Type | defines | Module | 1 | 0..* |
| Module | contains | Sensor | 1 | 1..* |
| Space | contains | Asset | 1 | 0..* |
| Asset Type | categorises | Asset | 1 | 0..* |
| Asset | is watched by | Sensor | 0..1 | 1..* |
| Metric | is measured by | Sensor | 1 | 0..* |
| Unit | is canonical for | Metric | 1 | 0..* |
| Sensor | produces | Reading | 1 | 0..* |
| Metric | is recorded in | Reading | 1 | 0..* |
| Subject (Space or Asset) | is the Subject of | Reading | 1 | 0..* |
| Device | has | Pairing Status | 1 | 1 |
| Device | has | Connection Status | 1 | 0..1 (only when Paired) |
| Device | may report | Fault | 1 | 0..* (only when Paired) |
| Fault | identified by | Fault Code | 1 | 1 |
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.
- A Module belongs to exactly one Hub: the Hub whose settings list it.
- A Module's Space is its Hub's Space. A Module never stores its own Space.
- A Sensor's Metric is fixed by its board. The user can't change it.
- A Sensor can only watch an Asset in its own Space. The Asset's Space must equal the Space of the Sensor's Hub.
- A Reading has exactly one Subject. If its Sensor watches an Asset, the Subject is that Asset; otherwise it is the Sensor's Space.
- A Reading never changes once recorded. Its Sensor, Metric, value, Unit and time are fixed.
- A Reading is stored in its Metric's canonical Unit. Other Units are only for display.
- Connection Status exists only when Pairing Status is
Paired. - A Floor and its Spaces belong to the same Site.
- A Hub gets a Space only after it is Paired. An Unpaired or Pairing Hub has no Space.
- 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.
- 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.
- A Site has exactly one owner. The Accounts it is shared with never include its owner.
- 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.
| Aggregate | Root | Contains | Points to (by ID) |
|---|---|---|---|
| Site | Site | Floors, Spaces, Assets | Account, Space Type, Asset Type |
| Hub | Hub | Modules, Sensors, Device Status | Space, Module Type, Metric, Asset |
| Reading | Reading | (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.