Workflow & Policy Engine
Clinical operations differ by ward, by encounter type, by insurance scheme, and by country. medOS handles that variation with configuration, not code: a deterministic rule engine that an operations admin can edit live, a workflow editor where the transition between two states is the unit of integration, and a direction of travel toward a single shared state-machine kernel that every hospital process plugs into.
Three design rules
These hold across every part of the engine and are worth stating up front.
| Rule | What it means |
|---|---|
| Deterministic | A gate is a pure predicate over a context object. The same inputs always produce the same decision. No randomness, no model in the decision path. |
| Fail-open | If the rule table is empty or unreachable, surfaces fall back to their prior hardcoded behavior. Day-one regression risk is zero. |
| Default-OFF | New rules ship as drafts; new editor surfaces sit behind feature flags. Nothing changes until an admin deliberately turns it on. |
Policy gates — the rule engine
The original problem: workflow checks like "payment must be settled before a specimen is collected" were hardcoded and duplicated across dialogs. That default was correct for routine outpatient flows but wrong for emergencies, VIP patients, insurance pre-authorization, and per-country market packs — and every variant needed a code change and a redeploy.
Policy gates replace those hardcoded checks with admin-editable rules. Each rule is a row of JSON: scope (who/what it applies to), predicate (what must be true to pass), and action (what happens when it fails). Admins manage them at an in-app admin page, and changes propagate to every open clinical screen in well under a second.
Anatomy of a rule
| Part | Holds | Examples |
|---|---|---|
| Scope | The dimensions a rule applies to — empty means "all" | product category, department, clinic, facility, encounter type, patient class, benefit scheme |
| Predicate | An all / any tree of conditions over a context | order.payment_status equals paid, patient.deposit_balance gte order.total |
| Action | What a failed predicate does | block, warn, or require_override, each with a severity and a bilingual message |
| Priority | Higher number wins when rules conflict | a priority-500 ER bypass overrides a priority-100 default gate |
Predicate operators include equality, set membership, numeric comparison, and existence checks. Field paths are dot-notation against the evaluation context, and a value can itself be a path, so cross-field comparisons (deposit vs. order total) work without special-casing.
How evaluation works
1. Load active rules whose trigger matches the attempted action
2. For each rule:
scope doesn't match the context -> skip
predicate empty OR predicate passes -> skip (satisfied)
otherwise -> the rule has FIRED
3. No rule fired -> not blocked
4. One or more fired -> sort by priority, take the top:
block -> blocked
warn -> warning shown, action allowed
require_override -> blocked, but an authorized user may override
The evaluator is a pure function with no I/O, so it is trivially testable and can be reused on the server. Each clinical screen builds a context from the data it has, asks the engine, and renders accordingly — falling back to its legacy check if no rules are present.
Admin page
Operations admins list, create, edit, and toggle rules in-app. A condition builder exposes the available fields, operators, and actions — no SQL, no deploy. Edits stream to open clinics over a realtime channel.
Per-screen evaluation
A screen builds a context and calls the gate hook; the decision drives the button, message, and severity.
Multi-dimensional
One table holds default and per-region rules side by side; market packs seed country-specific defaults.
Priority resolution
Higher priority wins. A bypass rule cleanly overrides a default gate without editing either.
Fail-open fallback
Empty or unreachable rule table means each dialog degrades to its prior hardcoded behavior — no regression.
The workflow editor — transitions as the unit of integration
A clinical workflow is not a bag of nodes with rules bolted on the side. The transition — the edge from one state to the next — is the thing that integrates everything. Every transition carries five slots:
| Slot | Question it answers |
|---|---|
| Trigger | What causes the transition to be attempted — a user action, an event, a timer, a webhook? |
| Participant | Who may fire it — which role, or a non-human automated actor? |
| Gate | A deterministic predicate that must pass for the transition to commit — the same policy-gate shape. |
| Action form | Which form or modal opens when the transition fires. |
| Effects | What the committed transition writes or emits downstream. |
Gates, queues, and worklists are one model
Once the transition is the single artifact, the surfaces clinicians see every day turn out to be projections of it, not separate systems:
┌─────────────────────────────┐
│ Transition (the edge) │
│ trigger · who · gate · │
│ form · effects │
└──────────────┬──────────────┘
┌───────────────────────┼───────────────────────┐
▼ ▼ ▼
Policy gate Department queue Clinician worklist
= the gate slot = patients waiting at a = the outgoing transitions
node for an outgoing from my patients' nodes
transition that I may fire and that
pass their gates
So the same gate that blocks an action also drives whether the button appears on the doctor's worklist. There is one definition of "what is allowed here," and the queue and worklist are views over it. This is additive: an edge with no configured transition behaves exactly as it does today, and the legacy work-list path is left untouched.
One transition, several validators
Three legality checks already exist in the system and continue to do their jobs — they become validators and projections of the canonical transition rather than competing definitions:
| Check | Validates |
|---|---|
| Design-time connection rules | Which node types may be wired together in the editor |
| Runtime state machine | Which concrete state may follow which, at execution time |
| Worklist action mapping | Which button moves a row to which state |
A transition may narrow what these allow, never widen it. None is removed; each is re-pointed to read the one shared object.
Toward a shared state-machine kernel
Pull the camera back and the same shape repeats across the hospital: admissions, transfers, discharge, every order type, blood bank, the operating room, labour, consults, claims, payments. Each is a state machine — states, transitions, guards, side-effects, and a timeline — and historically each was hand-built. That produced three recurring problems the kernel is designed to remove:
| Problem | Symptom | Kernel answer |
|---|---|---|
| Inconsistent emission | Some status changes notify downstream systems; some mutate silently and desync the read model | One emission choke point every status change funnels through |
| Colliding vocabularies | The same concept gets several enums across services | Status-as-data — states defined in one table, mapped to standard vocabulary |
| Scattered timestamps | Audit columns differ per entity; some processes have no timeline at all | One append-only timeline of record keyed by process and step |
The kernel is shared conventions and infrastructure, not a monolith. Bounded
contexts stay separate; they agree on a timeline of record, a status model, an
emission choke point, and column/print projections — with idempotency, a
concurrency guard, and append-only audit baked in. State buckets map onto
FHIR R4 resource vocabulary (Task, Encounter, ServiceRequest,
Procedure, Specimen, Condition, Appointment, and the financial resources)
so the canonical names are standard, while internal storage is unchanged.
A deliberate non-goal: medOS does not re-platform onto an external code-first workflow engine. The visual editor plus an event-sourced projection layer already provide an engine that grows with the hospital without a deploy. A heavier saga engine is reserved only for the genuinely hard, compensating flows.
Where automation fits
Automated actors can occupy the participant or effects slot of a transition, but under strict guardrails:
- Gates stay deterministic. An automated sensor may write a fact with provenance that a gate then reads; it never becomes the gate.
- Recommender-first. Generated work lands as a proposal for a human to accept, edit, or reject — never auto-final.
- Reversible-only and capped. An automated actor may only fire reversible, operational transitions, within an explicit autonomy cap, with mandatory provenance. Clinical-risk transitions stay human-only.
See also
- Architecture Overview — services, bus, and read model
- Data flow — write path, read path, and projection
- System topology — ports and service map
- Backend services — where transitions become writes of record
- FHIR integration — the canonical vocabulary for process state
- HL7v2 integration — legacy instrument and ADT triggers