Skip to content

ADR-007: Connect integrations without deep data integration

Date Author Status
2026-07-30 Fabian Beyerlein Draft

Context

The system landscape STREAM might have to integrate with is vast. And this vast system landscape comes with various levels of modernity, thus requiring different levels of integration. This usually comes down to available data. AMONDIS does not track data in the structure and level of detail we do - so we cannot reliably rebuild the data on our side properly.

Some integrations cannot become first-class STREAM master-data integrations. They provide enough data to render and operate electronic shelf labels, but not enough structured product, topology, packaging or inventory data to rebuild the STREAM domain model. For these integrations, STREAM should not invent incomplete products, locations or shop entities just to fit the existing model.

Instead, these integrations should be treated as display-driven external label integrations:

  • The external system owns product identity, location identity, replacement logic, validity and requestability.
  • STREAM owns ESL assignment, rendering, button press handling, assignment state and delivery to physical labels.
  • STREAM does not own product master data, shop catalog data, topology hierarchy or the external order lifecycle for these integrations.

The first integration we will implement will be on the simpler end of the spectrum in the first version. UHB Software1 sells Medical e-Shop2 as part of their AMONDIS3 product suite.

The first version of the integration will consist of a JSON file exchange through an SMB share. We will receive a JSON document with labels that contains all data we need to display on the ESL.

Example data

svwstamm.json
[
    {
        "Brcnum": 32542,
        "Otl": 466,
        "OtlAnfmaw": "931540",
        "OtlAbbr": "39 I ENTGIFT",
        "OtlDes": "39 I ENTGIFTUNG",
        "Abbrroom": "Gruppenraum",
        "Desroom": "E.01",
        "Abbrarck": "Disco",
        "Tray": "1",
        "Subtray": "1",
        "Item": 70986,
        "Itemnum": "9603919",
        "Itemcode": "eSPR KETAMIN 50MG/ML ROLLE",
        "ReplacementItemCode": null,
        "Qtt": 25,
        "Unit": "STK",
        "Pcksize": 1000,
        "Unitissue": "ROL",
        "ValidFrom": "20210922",
        "ValidUntil": "20991231"
    },
    {
        "Brcnum": 32631,
        "Otl": 324,
        "OtlAnfmaw": "912505",
        "OtlAbbr": "10 I LI ZI.0",
        "OtlDes": "10 I LI ZI.027 UMW.- UND KH-HYGIENE",
        "Abbrroom": "Test TL",
        "Desroom": "Test TL",
        "Abbrarck": "Test TL",
        "Tray": "1",
        "Subtray": "1",
        "Item": 72840,
        "Itemnum": "1000000.145",
        "Itemcode": "1,2-DICHLORETHAN REINST EMPURA  1 L (PCK)",
        "ReplacementItemCode": "eSPR KETAMIN 50MG/ML ROLLE",
        "Qtt": 1,
        "Unit": "PCK",
        "Pcksize": 1,
        "Unitissue": "PCK",
        "ValidFrom": "20230127",
        "ValidUntil": "20991231"
    }
]

Field descriptions

JSON field Description
Brcnum Unique ID of label;
required for sending requests to Medical e-Shop
Otl Supply Area ID
OtlAnfmaw Supply Area Code
OtlAbbr Supply Area short name
OtlDes Supply Area long name
Abbrroom Room short name
Desroom Room long name
Abbrarck Shelf short name
Tray Compartment Code
Subtray Sub Compartment Code
Item Medical e-Shop internal product ID
Itemnum Product ID
Itemcode Product name
ReplacementItemCode Product name of a potential replacement product;
show instead of Itemcode if present
Qtt Packaging unit packages
Unit Packaging unit item descriptor
Pcksize Packaging unit items
Unitissue Packaging unit package descriptor
ValidFrom Label valid/usable from
ValidUntil Label valid/usable until

Integration flow

Importing data from AMONDIS

  1. AMONDIS will drop JSON files into the SMB share
  2. Mercury reads them, validates the file and sends the imported labels to Solaris
  3. Solaris stores them as external labels

The import must be idempotent. A file from AMONDIS represents the complete current snapshot for one organization and integration. Rows present in a successfully imported file are upserted. Rows that were known before but are no longer present in the latest successful full import are marked as BLOCKED; if the integration provides validity dates, valid_until is retained from the source data. A background job should mark external labels where valid_until has passed as BLOCKED. External labels with status BLOCKED cannot be requested. If assigned, they are displayed like products in BLOCKED state.

Failed imports must not partially apply missing-row blocking. Either the import is applied as a complete snapshot, or the previous successful import remains the source of truth.

Sending requests to AMONDIS

The labels from AMONDIS need to be connected to ESLs. The order flow is roughly:

  1. Person clicks ESL
  2. Solaris receives webhook
  3. Solaris resolves the external label assigned to the ESL
  4. Assignment state is set to REQUESTED
  5. Data sent to Mercury
  6. Mercury drops a JSON file

We need to prevent re-orders for this integration. If the assignment is REQUESTED, do nothing. Refilling the label is always possible. There will need to be an endpoint in Mercury to receive notifications on cancelled requests from AMONDIS to properly reset labels.

For request button presses, Solaris must suppress requests when the linked external label has status BLOCKED or when the assignment state is already REQUESTED. Otherwise, it transitions the assignment state to REQUESTED and sends the request to Mercury.

For refill button presses, Solaris transitions the assignment state back to DEFAULT. This makes future requests possible again.

uml diagram

API definition (German)

Google Drive

Decision

Instead of trying to shoehorn the AMONDIS data into our structure, we introduce external labels. External labels allow us to repeat a similar pattern for other integrations that provide display-ready label data but no deep STREAM-compatible master data.

External labels and ESLs can be linked through product assignments. The assignment state remains separate from the external label's requestability:

  • DEFAULT means requests are possible if the external label is requestable.
  • REQUESTED means a request was already sent and duplicate requests must be suppressed.

The table shall have the following columns:

Name Data type Notes
id UUID v7
org_id UUID Edge to organizations
integration TEXT ID of integration responsible for this row
external_id TEXT Stable label/product ID from the external system
status TEXT Product status, e.g. ACTIVE or BLOCKED
data JSONB JSON blob with label data as received by external system
data_hash TEXT Hash of normalized data to avoid unnecessary updates
valid_from TIMESTAMPTZ Optional validity start
valid_until TIMESTAMPTZ Optional validity end
last_seen_at TIMESTAMPTZ Last successful full import containing this row
created_at TIMESTAMPTZ
updated_at TIMESTAMPTZ

The natural deduplication key is (org_id, integration, external_id). For AMONDIS Medical e-Shop, Brcnum is the external_id.

Each integration provides an adapter for the integration-specific behavior. The adapter is responsible for:

  • identifying the integration
  • validating and mapping imported data
  • extracting the external ID
  • deriving requestability
  • rendering labels or providing the label rendering model
  • generating outbound request payloads for Mercury

This way, integrations can define their own label templates and request payloads while the rest of the system does not have to understand integration-specific fields.

In the UI, we need a new section to manage external labels. Topology, Master Data and the Shop should eventually be hidden. The external labels section needs to allow seeing all synced external labels (read-only). It also needs to allow (bulk-)assigning external labels to ESLs through their label code as usual.

Analytics should continue to work for assignments backed by external labels.

Alternatives considered

  • Map AMONDIS data into STREAM products, topology and shop entities
    • Rejected because the source data is not structured enough for our model. Creating fake or incomplete domain entities would make the core model less reliable.
  • Store AMONDIS fields directly on existing product assignment entities
    • Rejected because it would pollute core assignment data with integration-specific fields and would not scale to other shallow integrations.
  • Build AMONDIS-specific tables
    • Rejected because future integrations with similar constraints would repeat the same pattern.
  • Render directly from files without persisting external labels
    • Rejected because assignment, search, analytics, idempotent imports and request suppression need persisted state.

Consequences

  • Enables shallow integrations without corrupting STREAM's core product, topology and shop models.
  • Keeps integration-specific data and behavior behind adapters.
  • Reuses the existing ESL assignment, button press event and assignment state infrastructure.
  • Adds a second label/product path next to the normal STREAM product model.
  • UI and analytics need to understand assignments backed by external labels.
  • Some STREAM features that require deep product, topology or shop data will not be available for these integrations.

Open decisions

  • adapter shape with interface
  • what does marcury do vs what does solaris do?
  • do we parse to internal data in mercury? do we pass the blob to solaris and keep mercury light?