Skip to content

Semantic composition for Product Applications #

Semantic composition is the published contract that turns Product Application records into curated Customer Portal workplaces. You declare facets (reusable semantic widgets), optional event streams, and page archetypes such as capacity. The portal renders those facets; you do not hand-author an arbitrary widget canvas for each customer.

Use this page when you are extending a first-class kit (for example silo monitoring) or reviewing composition JSON on a published Product Version. For the full facet and supporting-widget inventory, see Semantic widget catalogue.

Worked example: silo capacity #

Silo monitoring is the clearest first-class composition. One published contract produces a workplace grid: each silo record gets the same ordered facet stack, and hosts that share a record share the same overlay markers.

Capacity page fixture with North and South silo cards stacking current fill, asset status, and volume history
Capacity archetype: one card per silo record. Facets stack in composition order; overlays are selected once on the page.

The sections below show the JSON that produces that workplace, with the same fixture widgets used for local gallery review and docs captures.

What composition owns #

LayerResponsibility
Product Application models and fieldsCapacity, thresholds, device bindings, camera binding
semantic_composition on the published definitionFacets, relations, rollups, terminology, event streams
Portal pages (page_type + config)Which facets and overlays appear on a workplace page
Portal page data APIRecords, resolved facet payloads, and windowed overlay events

Composition does not auto-pair devices from a network. A silo record binds its level device and optional camera_device. The network remains the availability pool; the record is the pairing surface.

Where the contract lives #

StageLocation
Draft / OEM authoringProduct Application metadata (semantic_composition)
PublishedProductVersion.definition_json.semantic_composition
Portal instance payloadsemantic_composition on the Product Instance response
Page dataGET /api/portal/products/{id}/pages/{page_key}/data/

Kits ship a starting composition (for example silo_level). After a kit upgrade that adds fields or facets, republish the Product Application version and refresh attached portal instances so existing records pick up the new contract.

Facets #

Each facet maps one record model onto one semantic component:

json
{
  "facet_key": "silo_level_timeseries",
  "component": "timeseries",
  "record_model": "@model:silo",
  "sort_order": 15,
  "bindings": {
    "value": { "source": "stream_derived", "key": "current_volume" },
    "unit": { "source": "field", "key": "units" },
    "band_ll": { "source": "field", "key": "low_low_threshold" },
    "band_l": { "source": "field", "key": "low_threshold" },
    "band_h": { "source": "field", "key": "high_threshold" },
    "band_hh": { "source": "field", "key": "high_high_threshold" },
    "device_online": { "source": "device_presence", "device_field": "device" }
  },
  "freshness": {
    "telemetry_stale_after_seconds": 300
  }
}

Binding sources #

SourceResolves from
fieldProduct record field
stream_derivedStream-derived field on the record
streamLive Device Class channel sample
device_presenceOnline/offline for a bound device field
offline_syncOffline dataset sync row
constantStatic display default

Every facet must render explicit freshness (live, stale, offline, unknown) rather than presenting a last-known value as current truth. See Device data and control contract.

Current fill vs volume history #

These are different operator jobs. Do not reuse one label for both.

Facet componentOperator job
level_capacitySnapshot: quantity vs capacity and threshold attention now
timeseriesHistory chart: series over a window with HH/H/L/LL bands and markers
Current fill facet for North silo showing 38 of 120 with a vertical level gauge and live reading
Current fill (`level_capacity`): live quantity vs capacity, ullage, and threshold attention.
Volume history chart with HH H L LL band guides and vertical alarm markers on a shared time axis
Volume history (`timeseries`): zoomable series with HH/H/L/LL bands and overlay markers on one time axis.

In silo monitoring the capacity page shows current fill and a separate volume history chart in the same record stack.

Record composition stack with a needs-attention threshold facet above a healthy Circuit 3 resource meter
Record composition stack: ordered facets for one record. Child hosts can share the same overlay event list.

Page archetypes #

Composition page types used by first-class apps:

page_typeUse it for
capacityGrid of vessels/meters with facet stacks and shared overlays
boardLane board for work-in-progress records
timelineChronological event / setpoint / playback history
ledgerAppend-oriented tabular history
Board page archetype with queued, in_progress, and completed lanes each holding a transaction facet card
Board archetype: the same facet vocabulary arranged as WIP lanes rather than a capacity grid.

Workspace pages such as Overview, Work, Exceptions, Records, and Map remain available through Application Workspace presentation. Composition archetypes sit alongside those pages when the product needs a semantic workplace (for example Silo capacity).

Capacity page config #

json
{
  "key": "silo-capacity",
  "page_type": "capacity",
  "model_logical_key": "silo",
  "config": {
    "title": "Silo capacity",
    "primary_model": "@model:silo",
    "facet_keys": [
      "silo_level_capacity",
      "silo_level_timeseries",
      "silo_watch_camera"
    ],
    "overlay_event_keys": [
      "silo_band_alerts",
      "silo_band_crossings"
    ],
    "timeseries": {
      "series_field": "current_volume",
      "unit_field": "units",
      "bands": {
        "ll": "low_low_threshold",
        "l": "low_threshold",
        "h": "high_threshold",
        "hh": "high_high_threshold"
      },
      "window": "24h"
    }
  }
}

overlay_event_keys selects which composition event_streams feed the page. timeseries.window accepts values such as 24h or 7d and drives the default event query window on the page data API.

Event streams and shared overlays #

Declare streams once on the composition. Hosts that share a record (volume history chart and watch camera) consume the same normalized overlay points so markers align on one time axis.

json
"event_streams": [
  {
    "stream_key": "silo_band_alerts",
    "kind": "alarm",
    "source": "product_alerts",
    "alert_keys": [
      "silo_low_low",
      "silo_low",
      "silo_high",
      "silo_high_high"
    ]
  },
  {
    "stream_key": "silo_band_crossings",
    "kind": "threshold",
    "source": "band_crossings",
    "series_field": "current_volume",
    "bands": {
      "ll": "low_low_threshold",
      "l": "low_threshold",
      "h": "high_threshold",
      "hh": "high_high_threshold"
    }
  },
  {
    "stream_key": "silo_camera_motion",
    "kind": "motion",
    "source": "device_channel",
    "device_field": "camera_device",
    "channel": "motion"
  }
]
Volume history chart with low and low-low markers plus a legend listing threshold and alarm overlay events
Chart host: band crossings and product alerts become markers on the series. The legend lists the same normalized points.
Watch camera player with scrubber and a shared event marker rail beneath the preview
Media host: the same overlay points drive the scrubber marker rail so chart and camera stay aligned.

Stream sources #

sourceProducerNotes
product_alertsPortal page data (events)Open alerts plus recently resolved alerts in the window; filtered by alert_keys when set
band_crossingsClient from series + band fieldsThreshold crossings derived from the chart series
device_channelClient / device channelFor example camera motion on camera_device
record_eventsReservedApplication-specific record timeline events

Overlay event point shape #

Normalized points returned on page data (and mirrored by the portal semantic package) look like:

json
{
  "t": "2026-09-08T01:10:00.000Z",
  "t_end": "2026-09-08T01:25:00.000Z",
  "kind": "alarm",
  "severity": "warning",
  "label": "Low level",
  "source_ref": "silo_low",
  "record_id": "rec_…",
  "stream_key": "silo_band_alerts",
  "status": "open",
  "band": "l"
}

record_id scopes markers to one silo (or other record). Alerts that omit a record id are not treated as network-wide overlays for every vessel.

Portal page data API #

http
GET /api/portal/products/{product_id}/pages/{page_key}/data/

Useful query parameters for capacity / timeseries pages:

ParameterPurpose
windowRelative window (24h, 7d); defaults from page timeseries.window
since / untilAbsolute ISO bounds when you need an exact range

The payload includes page records, facet-ready values, and an events array of overlay points for the selected streams. The portal composition stack provides those events to timeseries and media hosts so the chart and camera scrubber stay aligned.

Operator model for silo monitoring #

  1. Place level and camera devices on a network (yard).
  2. Create or edit a silo record: bind device (level), optionally camera_device, capacity, and LL/L/H/HH thresholds.
  3. Open the Silo capacity workplace: current fill, volume history with bands and alarm markers, and watch camera with the same marker rail when a camera is bound.

Publish a new Product Version after kit changes that introduce timeseries, event_streams, or new threshold fields. Existing instances keep the older published definition until you roll them forward.

Platform and OEM engineers can review fixture-populated facets and supporting widgets without signing into a portal:

  1. Run the portal frontend locally.
  2. Open /dev/semantic-widgets.

Stable deep links (URL hash / data-docs-slug):

SlugWidget
#level-capacityCurrent fill snapshot
#timeseries-bandsVolume history with HH/H/L/LL bands
#event-laneEvent overlays on the history chart
#media-marker-railWatch camera + scrubber + shared marker rail
#page-capacityCapacity page archetype
#page-boardBoard page archetype
#record-composition-stackOrdered facet stack for one record

Capture [data-test="gallery-stage-<slug>"] for docs screenshots of the widget stage only. From dataplicity-documentation:

bash
SEMANTIC_WIDGET_GALLERY_URL=http://localhost:5175/dev/semantic-widgets \
  npm run capture:semantic-widget-gallery

The Semantic widget catalogue embeds those captures for every gallery entry.