Documentation / Conception / Tutoriels / Your first workflow

Your first workflow

In this lesson we build a small clinical workflow from scratch in the visual editor: a lab result arrives, and we compute the patient's estimated glomerular filtration rate (eGFR). We wire it up, test it on a forged event, save it, and run it for real — all without writing a line of code or a line of YAML by hand.

By the end you will have a saved, executable workflow and will have seen it compute a result twice: once in simulation, once in a real run.

Nothing to install: we use the Config Plane UI that ships with the platform. If you do not have an instance running yet, follow Bring up your first instance first, then come back here.

What we are building

Two things happen in our workflow:

  1. A trigger fires when a lab result is received — this establishes the patient/encounter context and hands us the result.
  2. A compute node takes that result plus the patient's demographics and returns an eGFR (using the CKD-EPI 2021 equation).

A workflow is a typed dataflow graph: we connect the typed output of one node to the typed input of another, and the editor checks type compatibility as we go. See The dataflow model for the why; here we just follow the steps.

Step 1 — Open the editor on a new workflow

Open the Config Plane and go to Workflows. In the workflow list header, click + New. The creation dialog opens with the Empty starting point selected.

Step 2 — Name the workflow and pick its trigger

In the dialog:

  • set Name to My first workflow — the file below it becomes my-first-workflow.workflow.yaml, where the workflow will be stored;
  • set Trigger to Lab result received. The Filter section appears below it. On the demonstration instance, bio-result.workflow.yaml already receives every lab result, and the dialog says so: once published, our workflow starts in addition to it. Our workflow takes only creatinine results: in Guided mode, click Add a condition, set Field to analyte, keep the operator equals, and type creatinine as the Value. The generated expression reads event.analyte == "creatinine".

Click Create. The editor opens on the new workflow. We already see one node on the canvas: the trigger, labelled Lab result received, with the event it receives, BioResultReceived, below its label — our starting point. Nothing is saved yet.

Click the trigger. The inspector shows its Declared event section: the event it receives, BioResultReceived, declared by the @posos/clinical plugin. Under Context, patient (patientId · required) and encounter (encounterId · required); under Fields, analyte (String, required), value (Decimal, required), unit (String) and the other fields of a lab result. This is where analyte in our filter comes from: a filter reads these fields of the event as it arrives. Below comes the Filter section with the condition we set in the dialog.

Notice the three panels: the palette on the left (trigger selector and a searchable node list), the canvas in the middle with a toolbar and the Build with AI panel, and the inspector on the right. The toolbar shows the name and, next to it, the target file.

Step 3 — Add the compute node

In the palette, type eGFR into the ⌕ Filter nodes… box. One entry appears: eGFR (CKD-EPI 2021). Click it.

A new node lands on the canvas and becomes selected. In the inspector on the right we can see its Identifier (auto-generated as ckd_epi_2021) and its type, clinical.egfr.ckd-epi-2021. This node has two required inputs: result (an Observation) and demographics.

Step 4 — Add the demographics source

Our compute node needs demographics, so we add a node that provides them.

Clear the filter box, type Load demographics, and click the Load demographics entry. This node (patient.load-demographics) reads the current patient's demographics and exposes them on its demographics output.

We now have three nodes on the canvas: the trigger, the eGFR compute node, and the demographics source.

Step 5 — Wire the nodes together

We connect ports by dragging from an output socket to an input socket. Each node shows its output ports on its right edge and its input ports on its left edge.

Make two connections:

  1. Drag from the trigger's result output to the eGFR node's result input.
  2. Drag from Load demographics' demographics output to the eGFR node's demographics input.

As each connection lands, the editor validates the two port types against each other and flashes a confirmation such as Connected: result → result. If the types were incompatible it would refuse the link and tell us why — this is the same static check the engine runs. For more on ports and this live validation, see Wire ports.

Both required inputs of the eGFR node are now fed. We have a complete little graph.

Step 6 — Simulate it

Before saving anything, we test the graph on a forged event. Simulation runs the current graph through the same interpreter as production, but with no durable run and no side effects.

Click ▶ Simulate in the toolbar. The inspector turns into the simulation panel, pre-filled for us:

  • a Subject field: the kind patient, chosen among the kinds the event establishes, and an empty identifier;
  • an Event (JSON) payload built from the trigger's example, with the placeholder identifier patient-exemple.

Type patient-42 in the Subject identifier: the simulation will read that patient's real record, and the event's patientId follows the subject:

{
  "type": "BioResultReceived",
  "patientId": "patient-42",
  "encounterId": "sejour-7",
  "analyte": "creatinine",
  "value": 160,
  "unit": "µmol/L",
  "display": "Créatinine",
  "loincCode": "14682-9",
  "status": "final",
  "interpretation": "H",
  "referenceLow": 45,
  "referenceHigh": 90
}

The simulation runs our graph on this event directly: it does not evaluate the trigger filter. The filter is tested in the trigger's Filter section.

Click ▶ Run the simulation. Within a moment an overlay appears on the graph: each node is marked ✓ executed (or ⤼ skipped, with the reason), and the computed values appear under the output ports. A flash message reports how long it took and how many writes were captured.

Click the eGFR node to open its full detail in the inspector — its inputs and its egfr output, exactly what it produced. That eGFR value is our first visible result.

Simulation is the fast feedback loop for authoring; see Simulate a workflow for its options (validation scenarios, captured writes, real vs. mock agents).

Step 7 — Save and publish it

We are happy with the graph, so we persist it. Click Publish, confirm, and leave the version note empty.

The editor first saves the graph as the workflow's draft — the first save creates the workflow — then publishes it: the server validates it (schema, dataflow, expressions, codes, terminology, webhook address) and, if it passes, version 1 becomes active and receives events. The confirmation and the publication result list the other workflows that start on the same event — here bio-result, with its effects: each creatinine result now starts both workflows. The editor then reloads on the saved workflow in editing mode. (Save the draft alone stores the graph without publishing it: a draft receives no events.)

What the editor stored is plain, readable YAML — this is the single source of truth for our workflow:

apiVersion: meridian/v3
name: My first workflow
context:
  - patient
  - encounter
trigger:
  type: trigger.bio-result-received
  selector: event.analyte == "creatinine"
nodes:
  load_demographics:
    type: patient.load-demographics
  ckd_epi_2021:
    type: clinical.egfr.ckd-epi-2021
connections:
  - from: trigger.result
    to: ckd_epi_2021.result
  - from: load_demographics.demographics
    to: ckd_epi_2021.demographics

For the full grammar of this file, see the Workflow DSL reference.

Step 8 — Run it for real

Now we run the workflow as a durable execution. In the Config Plane, go to Executions and click + Emit an event.

In the composer:

  1. Under Target workflow, pick my-first-workflow.workflow.yaml.
  2. The Event payload (JSON) is pre-filled from the trigger's example. A required context id that the example does not carry gets a placeholder named <kind>-exemple: replace "patientId": "patient-exemple" with "patientId": "patient-42", the patient we simulated with.
  3. Click ▶ Send the event.

A new run appears in the list and the composer closes onto its detail. The timeline shows the event being received and each node executing, and the run reaches completed. This is the same computation we saw in simulation, now as a real, durable run.

Because we picked a Target workflow, only my-first-workflow started, and its filter was not evaluated. With None: every workflow that receives the type, the same event starts every published workflow that receives BioResultReceived: bio-result as well as ours, whose filter keeps this creatinine result. Each gets its own run, with the id <eventId>:<workflow file name without .workflow.yaml>.

To watch runs, filter them, and inspect their timelines in general, see Monitor executions. For why these runs are durable and resumable, see Why durable execution.

What we did

We built a workflow entirely in the editor — a trigger and a compute node, connected by type-checked wires — then simulated it, saved it as YAML, and ran it for real. Every step gave us a visible result.

Where to go next

97 documents11 sectionssource : /docs · généré au build