> ## Documentation Index
> Fetch the complete documentation index at: https://docs.coreflux.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Designing your data layer

> Topic trees (UNS), payload formats (scalar vs JSON), Models, and inheritance — patterns for how data is shaped and addressed.

## Designing Your Topic Tree

Your topic tree is the backbone of every Coreflux project. It decides how Actions trigger, how Models publish, how Routes map data into databases, and how dashboards subscribe. Get it right early and the system stays clean as it grows. Get it wrong and you'll be untangling spaghetti for years.

A **Unified Namespace (UNS)** is the idea that every piece of data in your system has one predictable address — a single MQTT topic — that any consumer can subscribe to without knowing or caring where the data physically came from. The concept applies to any domain: a factory floor, an office tower, a solar park, a city's traffic lights, or a fleet of delivery trucks.

<Tip>
  **Like a postal address that works anywhere in the world.** "Country / State / City / Street / Building / Apartment" lets a letter find any home, even one the postman has never visited. A UNS does the same for your data — a fresh subscriber can read or write to any device without ever being told it exists.
</Tip>

### When to Reach for This

Always — but the deeper hierarchy patterns earn their keep once you have more than a handful of devices, more than one site, or more than one team subscribing to the data. Even a single-device hobby project benefits from a consistent two-level structure.

### Common Principles Across Every UNS

Whatever your domain, a good hierarchy answers three questions:

1. **Who owns this data?** Top levels usually identify the organization or operator.
2. **Where does it physically live?** Middle levels mirror the real-world layout — sites, buildings, zones, machines.
3. **What is it?** The leaves identify the actual measurement, status, or event.

The deeper you go, the more specific the topic. Subscribers can pick exactly the level they care about and ignore the rest.

### Functional namespaces

Organize topics into functional namespaces that reflect the data lifecycle. These prefixes work alongside any domain hierarchy (ISA-95, buildings, fleets):

| Namespace    | Purpose                                      | Example Topics                                       |
| ------------ | -------------------------------------------- | ---------------------------------------------------- |
| `sensors/`   | Raw incoming sensor data                     | `sensors/temp001/raw`, `sensors/+/temperature`       |
| `processed/` | Transformed or enriched data                 | `processed/temp001/fahrenheit`, `processed/+/status` |
| `alerts/`    | Threshold violations and notifications       | `alerts/critical/pump03`, `alerts/+/temperature`     |
| `config/`    | Configuration values read by Actions         | `config/setpoint`, `config/max_temperature`          |
| `state/`     | Internal persistent state (used with `KEEP`) | `state/counter`, `state/last_run`                    |
| `cache/`     | Cached values for quick reference            | `cache/temp001/last_reading`                         |
| `system/`    | Heartbeats, health checks, diagnostics       | `system/heartbeat`, `system/status`                  |
| `commands/`  | Inbound instructions from external systems   | `commands/devices/+/restart`                         |

### Designing for Your Domain

Different domains use different hierarchy patterns. Here are five common ones — pick the closest match to yours and adapt.

#### Manufacturing — ISA-95

For factories, the industry standard is **ISA-95**, which divides operations from the enterprise level down to individual machines. The full MQTT path is a single string; the folder view below shows how each **topic level** lines up with **ISA-95** (Enterprise → Site → Area → Line / Cell → Device → **Metric** at the leaf).

`acme/dallas/packaging/line1/filler01/temperature`

```
acme/                               enterprise
└── dallas/                         site
    └── packaging/                  area
        └── line1/                  line / cell
            └── filler01/           device
                └── temperature     metric
```

The ISA-95 levels: **Enterprise / Site / Area / Line / Cell / Device / Metric**. Use it for any project where you have a corporate hierarchy that maps onto a physical plant layout.

#### Smart Buildings

Buildings have a natural physical hierarchy: campus → building → floor → zone → device. The same operator prefix can branch like a folder tree:

```
acmecorp/
└── headquarters/
    ├── floor3/
    │   └── conference_a/
    │       ├── thermostat/
    │       │   └── temperature
    │       └── lighting/
    │           └── state
    └── garage/
        └── level_b1/
            └── sensors/
                └── co2
```

Subscribe to `acmecorp/headquarters/floor3/#` to see every reading on the third floor. Subscribe to `acmecorp/+/+/+/thermostat/temperature` to compare temperatures across every conference room in the company.

#### Energy and Solar Parks

Energy operators typically organize by site, then by physical infrastructure (arrays, strings, inverters). One site can group weather separately from the array:

```
solarop/
└── lisbon-park/
    ├── array-a/
    │   └── string-3/
    │       └── inverter-01/
    │           ├── power
    │           └── voltage
    └── weather/
        └── irradiance

windop/
└── north-sea-cluster/
    └── turbine-12/
        └── rotor_speed
```

A maintenance team subscribes to `solarop/lisbon-park/#` for one park. A fleet operations center subscribes to `solarop/+/+/+/+/power` to monitor every inverter across every park.

#### Smart Cities

Cities organize by district, then by asset type (since asset categories are the meaningful grouping, not departments):

```
lisboa/
├── baixa/
│   ├── traffic_lights/
│   │   └── tl-042/
│   │       └── state
│   └── parking/
│       └── sensor-117/
│           └── occupied
└── parqueeduardo/
    └── air_quality/
        └── aq-03/
            └── pm25

porto/
└── centro/
    └── waste_bins/
        └── bin-218/
            └── fill_level
```

A transport team subscribes to `lisboa/+/traffic_lights/#`. An air quality dashboard subscribes to `+/+/air_quality/#` to see every monitor in every city.

#### Vehicle Fleets

Fleets organize by operator, region, vehicle type, and individual vehicle:

```
deliveryco/
└── iberia/
    ├── vans/
    │   └── v-2840/
    │       ├── location
    │       └── fuel_level
    └── refrigerated/
        └── r-118/
            └── temperature
```

A regional dispatcher subscribes to `deliveryco/iberia/#`. A predictive-maintenance system subscribes to `+/+/+/+/engine/diagnostic_codes` across every fleet.

### Optional: Data Classification

Many UNS designs add a classification segment after the device, splitting **measurements**, **state**, **events**, and **alarms** into separate branches under the same device:

```
acmecorp/
└── headquarters/
    └── floor3/
        └── conference_a/
            └── thermostat/
                ├── metrics/
                │   └── temperature
                ├── state/
                │   └── mode
                ├── alarms/
                │   └── sensor_fault
                └── events/
                    └── setpoint_changed
```

This makes it trivial to subscribe to "all alarms in the building" (`acmecorp/headquarters/+/+/+/alarms/#`) without picking up a flood of ordinary readings. Use it when your project has clearly different *kinds* of data per device.

### Don't Over-Engineer Small Projects

A single-room weather station or a hobby solar setup doesn't need six levels of hierarchy. Even a flat `sensors/<device_id>/<metric>` is fine until you have enough scale to need more. Pick a structure that fits today's complexity, and leave room to grow.

***

## Choosing Data Formats

Your topic tree answers **where** data lives. The next decision is **what shape** each message carries — a single value on the payload, a JSON object, or structured JSON produced by a **Model**. These choices are independent: you can mix scalar topics, JSON ingress, and Model output in the same project.

<Tip>
  **Like labels on shipping boxes.** The address (topic) tells you which shelf it belongs on; the box contents (payload) tell you what's inside. Sometimes one number on the label is enough; sometimes you need a packing list (JSON); sometimes you standardize the box size (Model) so every downstream handler knows what to expect.
</Tip>

### Scalar payloads (value only)

Use a **plain payload** — one number, string, or boolean with no JSON wrapper — when the **topic name already identifies** the measurement and you only ever send one value per message.

| Best for                                                 | Example topic                            | Payload       | In Actions          |
| -------------------------------------------------------- | ---------------------------------------- | ------------- | ------------------- |
| PLC registers, simple sensors, Modbus/OPC-UA mapped tags | `acme/dallas/line1/filler01/temperature` | `23.5`        | `PAYLOAD AS DOUBLE` |
| Status flags, on/off                                     | `.../running`                            | `true` or `1` | `PAYLOAD AS BOOL`   |
| Commands with a single argument                          | `commands/device/restart`                | `now`         | `PAYLOAD AS STRING` |

```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
DEFINE ACTION CheckHighTemperature
ON TOPIC "acme/dallas/+/+/+/temperature" DO
    SET "temp" WITH PAYLOAD AS DOUBLE
    IF {temp} > 80 THEN
        PUBLISH TOPIC "alerts/high_temperature" WITH {temp}
```

**Practices:**

* Put meaning in the **topic path** (site, device, metric) so subscribers do not have to parse the body.
* Prefer scalar leaves under `processed/` or your UNS metric level when data is already normalized at the edge.
* Avoid cramming multiple measurements into one topic without JSON — use separate topics or switch to JSON.

### JSON payloads (structured body)

Use **JSON** when one MQTT message carries **several fields**, nested data, or metadata (timestamps, units, quality flags) that do not belong in the topic string.

| Best for                               | Example topic                 | Payload shape                                         |
| -------------------------------------- | ----------------------------- | ----------------------------------------------------- |
| Devices, gateways, REST/webhook Routes | `sensors/temp001/telemetry`   | `{"temperature":23.5,"humidity":65,"unit":"celsius"}` |
| Partner APIs with variable schemas     | `integrations/partner/events` | Nested objects and arrays                             |

In **Actions**, extract fields with [`GET JSON`](/v2.0/lot-language/actions/operations#get-json). For nested paths, missing keys, and publishing cleaned JSON from an Action, see **[Working with JSON](/v2.0/lot-language/usage-patterns/working-with-json)** — that page is the hands-on guide; this section is about when to choose JSON at the data-layer level.

**Practices:**

* Keep **raw JSON** on ingress topics (`sensors/`, `integrations/`) and publish **normalized** data to `processed/` (scalar or Model output) so dashboards and databases see a stable shape.
* Always cast types in Actions (`AS DOUBLE`, `AS STRING`) — do not rely on implicit conversion.
* If many consumers need the same JSON schema, promote the shape with a Model instead of rebuilding JSON manually in every Action.

### Models (typed, consistent output)

A **Model** defines the JSON schema your broker publishes — field names, types, and triggers. Use Models when **multiple subscribers** (Routes, dashboards, other Actions) must see the **same structure** every time.

| Approach                                             | When to use                                                     | How it publishes                                                                            |
| ---------------------------------------------------- | --------------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
| **Triggered Model** (`WITH TOPIC` + `AS TRIGGER`)    | One device type, automatic publish when a source field updates  | Broker builds JSON when the trigger field changes                                           |
| **COLLAPSED Model** + `PUBLISH MODEL` from an Action | You control timing, destination, or merge JSON + scalar sources | Action decides when and where to publish                                                    |
| **Base + `FROM` inheritance**                        | Families of devices share most fields                           | See [Standardizing data across device types](#standardizing-data-across-device-types) below |

Triggered example — raw scalar in, structured JSON out:

```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
DEFINE MODEL SensorReading WITH TOPIC "acme/dallas/line1/filler01/formatted"
    ADD STRING "device_id" WITH "filler01"
    ADD DOUBLE "temperature" WITH TOPIC "acme/dallas/line1/filler01/temperature" AS TRIGGER
    ADD STRING "unit" WITH "celsius"
    ADD STRING "timestamp" WITH TIMESTAMP "UTC"
```

Action-driven example — JSON in, COLLAPSED Model out (full walkthrough in [Working with JSON](/v2.0/lot-language/usage-patterns/working-with-json#end-to-end-extract-json-and-publish-a-model)):

```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
DEFINE MODEL ProcessedSensorReading COLLAPSED
    ADD STRING "device_id"
    ADD DOUBLE "temperature"
    ADD STRING "unit"
    ADD STRING "timestamp"

DEFINE ACTION NormalizeTelemetry
ON TOPIC "sensors/+/telemetry" DO
    SET "sensor_id" WITH TOPIC POSITION 2
    SET "temp" WITH (GET JSON "temperature" IN PAYLOAD AS DOUBLE)
    PUBLISH MODEL ProcessedSensorReading TO "processed/" + {sensor_id} WITH
        device_id = {sensor_id}
        temperature = {temp}
        unit = "celsius"
        timestamp = TIMESTAMP "UTC"
```

**Practices:**

* Use **scalar topics** for simple ingestion; use **Models** for the contract your analytics and APIs depend on.
* Do not hand-build JSON strings in Actions when `PUBLISH MODEL` can do it — fewer quoting bugs and consistent field names.
* Mark the **primary measurement** as `AS TRIGGER`, not timestamps or metadata ([schema definition](/v2.0/lot-language/models/schema-definition)).
* Exploding incoming JSON into per-field topics (Models overview) helps when other Models or Actions need single fields without `GET JSON` on every step — see [Models overview](/v2.0/lot-language/models/overview).

### How the three formats work together

A common pipeline:

1. **Ingress** — device sends JSON to `sensors/+/telemetry` (or scalar to `.../temperature`).
2. **Action** — optional `GET JSON`, validation, unit conversion.
3. **Model** — publishes stable JSON to `processed/+/formatted` (or triggered Model from scalar sources).
4. **Route** — stores `processed/#` or specific leaves in a database.

```mermaid theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
flowchart LR
  device[Device or Route]
  rawTopic[Raw topic scalar or JSON]
  action[Action optional]
  model[Model]
  processed[processed/ topics]
  consumers[Dashboards Routes Actions]

  device --> rawTopic
  rawTopic --> action
  rawTopic --> model
  action --> model
  model --> processed
  processed --> consumers
```

| If your payload is…            | Prefer                         | Avoid                                                          |
| ------------------------------ | ------------------------------ | -------------------------------------------------------------- |
| One number per topic           | Scalar + topic semantics       | JSON with a single `"value"` field unless the device forces it |
| Multiple fields per message    | JSON ingress + Action or Model | Parsing JSON with string splits in Actions                     |
| Same schema for many consumers | Model on `processed/`          | Copy-pasting JSON templates in five Actions                    |
| Variant device types           | Base Model + `FROM`            | Separate unrelated schemas per device                          |

***

## Standardizing Data Across Device Types

When several devices in your system share most of their data fields but each has a few extras, define the shared fields in a **base Model**, then extend it for each variant. A LoT Model is essentially a typed schema that publishes structured JSON onto a topic. Inheritance works exactly like in object-oriented programming — the base holds what's common, the children add what's specific. Full reference: [Model Inheritance](/v2.0/lot-language/models/model-inheritance).

<Tip>
  **Like a base recipe for bread dough.** Sourdough, focaccia, and pizza all start from the same base — they just add their own toppings. Improve the base recipe, and every variant gets the improvement automatically.
</Tip>

### When to Reach for This

When your system has more than one *kind* of device that mostly produces the same data — pumps and chillers, vans and trucks, conference rooms and labs. The break-even point is around three variants: at two it's still tempting to copy-paste, at three the inheritance pays off, at five it's the only sane way.

### The Pattern

A smart-building example: every room in the building has temperature, humidity, and occupancy sensors. But conference rooms also need to track presentation equipment, and labs need extra safety monitoring. Start with the shared base — it's `COLLAPSED`, meaning it doesn't publish on its own and exists only to be inherited:

```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
DEFINE MODEL BaseRoom COLLAPSED
    ADD STRING "room_id"
    ADD DOUBLE "temperature"
    ADD DOUBLE "humidity"
    ADD INT "occupancy"
    ADD STRING "status"
    ADD STRING "timestamp"
```

A standard office uses the base directly. Specialized rooms extend it using `FROM`:

```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
DEFINE MODEL ConferenceRoom FROM BaseRoom
    ADD BOOL "projector_on"
    ADD BOOL "video_call_active"
    ADD INT "scheduled_meetings"

DEFINE MODEL Lab FROM BaseRoom
    ADD BOOL "fume_hood_running"
    ADD DOUBLE "co2_ppm"
    ADD STRING "hazmat_status"
```

`ConferenceRoom` automatically has all six fields from `BaseRoom` plus three of its own. `Lab` gets the six base fields plus three lab-specific ones. If you later add a `last_cleaned` field to `BaseRoom`, every child Model gains it without any edits.

**Two rules of thumb for designing the base:**

* Put a field in the base only if **every variant** truly needs it. Identifiers, timestamps, status, and core measurements belong in the base. Type-specific fields belong in the children.
* Don't put `AS TRIGGER` on a base Model field. The base exists to be inherited — it's the children that decide when and how they publish, either through their own trigger or via `PUBLISH MODEL` from an Action.

The payoff: every team consuming room data — facilities dashboards, energy management, AI agents — sees a consistent shape. They can rely on `room_id`, `temperature`, and `occupancy` being there for any room, and only need to handle the extra fields for variants they actually care about.

The same pattern fits anywhere you have a family of related devices: `BaseInverter` with grid-tied and hybrid variants in solar, `BaseSensor` with temperature, vibration, and pressure variants in industrial, `BaseVehicle` with van, truck, and refrigerated variants in fleets.

For JSON-heavy ingress before you standardize with Models, see [Choosing data formats](#choosing-data-formats) and [Working with JSON](/v2.0/lot-language/usage-patterns/working-with-json).

***

## Next Steps

<CardGroup cols={2}>
  <Card title="Working with JSON" icon="brackets-curly" href="/v2.0/lot-language/usage-patterns/working-with-json">
    GET JSON patterns, nested payloads, and PUBLISH MODEL workflows.
  </Card>

  <Card title="Model Inheritance" icon="diagram-project" href="/v2.0/lot-language/models/model-inheritance">
    Deep dive on `FROM`, base models, and extending schemas.
  </Card>
</CardGroup>
