O3 Order Templates
Status: REQUIREMENTS IN PROGRESS
Technical Complexity: HARD / COMPLEX
Big Picture:
Templates are orderables. There is no "choose a template" step — one search box, no separate
Drug OrdersandLab Orderssearch areas. A template is just another row the prescriber can pick, and order sets appear in the same list: abundleis one more kind of result, whose members each resolve through their own template.Searching returns the best way to order each thing: where a template exists, the templated result is the hit (each common regimen its own result, rendered in plain language with {blanks} for anything undefaulted) — a blank row is never shown beside it. Every result offers Add (straight to the basket — one click when complete) and Edit (the standard order form, pre-filled). Everything stays editable; nothing ever auto-signs.
Templates assist, never restrict: no template, broken template, or unknown type — the prescriber always gets the normal blank form. Nothing is ever un-orderable for lack of a template.
Templates are generic and type-driven: the same mechanism serves drug orders, test orders, and order sets, and new order types join by registering a handler.
1. Discussions held
Discussion | Link |
|---|---|
Drug Orders: Order Templates modelling (primary thread, 47 posts, 2022–2024) | https://talk.openmrs.org/t/drug-orders-order-templates-modelling/36835 · p2 · p3 |
2022-06-13 TAC: Order Templates to Support Drug Orders | https://talk.openmrs.org/t/2022-06-13-tac-order-templates-to-support-drug-orders/36859 |
Urgent TAC Request: Modelling for Drug Orders in 3.x (Formulary vs Prescription vs …?) | |
O3: Drug Orders / e-Prescribing | https://talk.openmrs.org/t/o3-drug-orders-e-prescribing/37560 |
OpenMRS Data Model support for Drug Administration Guidelines & FHIR | https://talk.openmrs.org/t/openmrs-data-model-support-for-drug-administration-guidelines-fhir/35530 |
Drug Order Model (earlier, 2018) | |
Backend module | |
O3 frontend |
2. Summary of discussions & decisions reached
An order template provides the default (and optionally alternative) values for one common way of placing a specific order (a drug order, a test order, etc.). It assists; it never gates orderability.
A template = a
type(versioned URI) + a target orderable (drug, concept, or test) + default field values, with blanks for anything undefaulted. Thetyperoutes to anOrderTemplateHandler<TemplateType>that (a) resolves the template into a draft Order and (b) renders it as human-readable text for search results and review (e.g.metoprolol {dose} mg by mouth {frequency}). Field structure varies by type — dosing for drug orders, specimen/urgency for test orders — which is exactly why the model is type-driven rather than drug-shaped.Templates assist; they do not restrict. An orderable with no template must still be fully orderable via a blank order form.
Templates are separate from the formulary. The
drugtable is the inventory (name, form, strength). The template is the "how it's given" layer, often authored by a different body (Ministry, clinical board, pharmacy). For test orders, the equivalent split is the test dictionary vs. the template.
Settled decisions
Template = a separate layer from the formulary/inventory; it captures how an orderable is usually placed.
One template = default values for one ordering option (not a dose×route×freq matrix); uncommon usages fall back to a blank form.
A drug can have multiple templates (e.g. metformin 500 mg BID and 1 g BID) — the clinician picks one.
Template
typeis a versioned URI and is behaviour-driving: it selects theOrderTemplateHandlerthat resolves and renders the template. This is the primary mechanism that keeps templates generic rather than drug-specific.V1 supports two drug-order dosing types (Simple + Free-text, matching
DrugOrder.dosingType) and at least one non-drug order type (simple test order), to prove the type-driven model works across order typesThree type URIs are registered in V1:
…/order/template/drug/simple/v1and…/order/template/drug/freetext/v1(both owned by the drug handler, which branches ondosingType), and…/order/template/test/simple/v1(test handler).The template targets an orderable (drug or concept for drug orders; a test concept for test orders); concept-level fallback still applies.
Coded fields (units/route/frequency) reference concepts (authoritative); a display name may be cached for readability.
Fields may hold arrays of
{value, default}alternatives, but V1's agreed initial compromise is to apply the default only and not surface choices yet.Dose is a numeric double (allows 0.5); dose units are a concept.
When a template exists, it replaces the blank result (Burke, squad call): the templated row is the search hit, with Add | Edit; a blank row appears only for orderables that have no template. Multiple templates: each remains its own result; what to display when many accumulate needs thought as content grows.
No interstitial screens (Burke, Ian): Add → basket; Edit → the standard order form pre-filled by the template. A template that fills only some fields adds as an incomplete order — exactly as if someone had half-filled the form by hand.
The UI does not label results as "drug" vs "template" (Ian) — an implementation detail providers should never see. Clinical cues (e.g. "General default — verify dose for this strength") remain.
Rendering rule: a field holding choices with no default renders as a {blank}; the form presents the choices.
Order sets commit as a unit (Burke): every group carries authored defaults, so Add accepts the defaults and puts the whole selection in the basket in one click; Edit opens the set panel to change the choices (choose-exactly-one / choose-any) before committing. Orders never enter the basket one-by-one midway.
Template schema
1: Simple Dosing
// Simple dosing — Amoxicillin 500 mg PO q8h × 7 days, #21, 0 refills
{
"type": "https://schema.openmrs.org/order/template/drug/simple/v1",
"dosingType": "org.openmrs.SimpleDosingInstructions",
"dosingInstructions": {
"dose": [{ "value": 500, "default": true }],
"unit": [{ "value": "tablet", "valueCoded": "<tablet-uuid>", "default": true }],
"route": [{ "value": "Oral", "valueCoded": "160240AAA…", "default": true }],
"frequency": [{ "value": "Every 8 hours", "valueCoded": "<q8h-uuid>",
"frequencyPerDay": 3, "default": true }],
"asNeeded": false,
"asNeededCondition": null,
"instructions": [{ "value": "Complete the full 7-day course.", "default": true }],
"indication": [{ "value": "Bacterial infection", "default": true }], // (optional; maps to the order's non-coded reason; enables one-click where an indication is required)
"duration": [{ "value": 7, "default": true }], // (optional)
"durationUnits": [{ "value": "Days", "valueCoded": "1072AAA…", "default": true }], // (optional)
"quantity": [{ "value": 21, "default": true }], // (optional; computed from duration when omitted)
"quantityUnits": [{ "value": "tablet", "valueCoded": "<tablet-uuid>", "default": true }],
"numRefills": [{ "value": 0, "default": true }] // (optional)
}
}One convention throughout: every field — dosing and dispensing alike — is an array of
{value, default}entries insidedosingInstructions. All five dispensing fields are optional: omit them and the order form leaves duration blank (a prescriber decision) and auto-computes the quantity once a duration is entered. This matches the seed payloads in §6.5 and the deployed data shape ratified in §9. (The test-order schema below is unaffected — its flatconcept/urgency/instructionsfields are correct for that type.)
2: Simple Test Order
// Simple test order — Renal function panel. Different fields, same model.
{
"type": "https://schema.openmrs.org/order/template/test/simple/v1",
"orderType": "testorder", // routes to the test-order handler
"concept": { "value": "Renal function panel", "valueCoded": "<renal-panel-concept-uuid>" },
"urgency": "ROUTINE",
"instructions": [{ "value": "Fasting not required.", "default": true }]
}3. Problem
Clinicians re-enter structured details for common orders (drugs and routine tests) even for the handful of ways each is almost always placed. This is slow, error-prone, and inconsistent across providers.
The community produced a backend module (openmrs-module-ordertemplates) and a proof-of-concept, but the effort stalled on modelling questions (dose/unit shape, where the domain lives, dispensing) and, critically, the initial flow made templates mandatory to order a drug — the exact coupling the community rejected. We need a documented, agreed V1 that:
pre-fills the order form from a template when one exists,
never blocks an orderable from being ordered,
keeps the template vocabulary cleanly separated (SIG is a drug-only term; test orders use their own fields)
and gives implementers a way to see and close coverage gaps.
4. User Stories
Common order (Amoxicillin): As a prescriber, when I search for a drug that has a template, I want the templated result to BE the hit — amoxicillin 500 mg by mouth every 8 hours for 7 days, in place of a blank row — with Add and Edit, so one click puts it in the basket, or Edit opens the pre-filled form for the exceptions.
Several ways to order (Metformin): As a prescriber, when a drug has more than one common regimen (e.g. metformin 500 mg twice daily and 1 g twice daily), I want each shown as its own search result rather than hidden behind a picker, so choosing the right one is a single click.
Common test order (Renal function panel): As a prescriber, when I order a routine test, I want it pre-filled and one-click through the same template mechanism as drugs — e.g. Renal function panel — routine — so templates work across order types, not just drugs.
Uncommon order: As a prescriber, when an orderable has no template, I want a fully usable blank order form so I can still order it. Nothing is ever un-orderable for lack of a template.
Concept default (Amlodipine): As an implementer, I want to attach one template to a concept and have it pre-fill every formulation of that concept that lacks its own, so I don't maintain a template per drug-table row.
Free-text SIG (Diazepam): As a prescriber ordering a drug whose instructions don't fit structured dosing — e.g. a diazepam reducing course, where the dose changes day to day — I want a free-text template that pre-fills the full instruction text.
PRN order (Paracetamol): As a prescriber ordering an as-needed drug, I want the template to pre-fill the PRN flag, the indication, and an explicit dispense quantity — because there is no treatment duration to compute a quantity from.
Dispense quantity (Amoxicillin): As a prescriber, once a duration is set on a template-seeded order, I want the quantity-to-dispense auto-calculated (dose-form-units × frequency/day × duration) so I don't compute it by hand.
Readable rendering: As a prescriber, I want a template shown as plain text with its defaults filled in and blanks left for anything undefaulted (e.g. metoprolol {dose} mg by mouth {frequency}) — both in search results and on review — so I always know exactly what I'm about to order.
Coverage visibility: As an implementer, I want a report of which orderables have no template (directly or via concept fallback) so I can close the gaps for the ones I actually order.
Acceptance Criteria
Template resolution, rendering & defaults. Templates surface directly in search and pre-fill the order form; the prescriber edits only exceptions.
Given ≥1 active template exists for an orderable (drug, concept, or test), searching returns each ready-made way to order it as its own result, rendered readably (e.g. amoxicillin 500 mg by mouth every 8 hours) — in place of the blank row for that orderable, not beside it. Every result offers Add and Edit.
Add puts the order straight into the basket with no intermediate screen; Edit opens the standard order form with the template's values filled in. No template-specific screen exists anywhere in the flow.
A template is rendered as a human-readable string with its defaults filled in and blanks shown for any undefaulted field (e.g. metoprolol {dose} mg by mouth {frequency}) — in search results and on review.
If an orderable has several common templates, each appears as its own result (no dedicated picker required).
Adding a template pre-fills the order per its payload.
If no drug-specific template exists, a concept-level template is used when present.
If no template exists, the blank order form is shown (no error) — nothing is un-orderable for lack of a template.
All pre-filled values remain editable before sign; nothing is auto-signed.
Clicking a pinned drug (My pinned drug orders) resolves its template through the same path (drug-level first, concept fallback); no template, missing privilege, or any fetch failure opens the blank form; repeat clicks while the fetch is in flight are ignored.
A template that supplies every field the order form itself would require yields a basket item not flagged Incomplete — addable and signable without ever opening the form. An item missing any required field is flagged Incomplete and must be completed in the form before signing.
Concept vs orderable specificity.
Templates bound to a specific drug/orderable match only that one.
Templates bound to a concept only (no specific orderable) apply to any formulation of that concept lacking a more specific template.
Retired templates are never returned or applied — enforced by the backend search, so the frontend never receives retired templates and does not filter them itself. (The module enforces this for V1; core order_template inherits it after the §6.6 Stage-1 migration.)
Order types & handlers. The model is
type-driven, not drug-shaped: each templatetyperoutes to anOrderTemplateHandlerthat resolves it into a draft order and renders it.
Drug, simple dosing — supplies dose, units, route, frequency, asNeeded, asNeededCondition, and patient instructions (wire key
instructions— array of{ value | text, default }; the default entry pre-fills patient instructions).Drug, free-text dosing — supplies an
instructionstext SIG.Where a schedule can't be expressed as a single dose × frequency — a changing-dose taper being the clearest case — use a free-text template carrying the whole SIG as text. (Diazepam: "5 mg three times a day for 2 days, then twice a day for 2 days, then at night for 2 days, then stop.") A minor timing note on an otherwise-simple order (e.g. "take the first dose at bedtime") rides along in the simple template's
instructions.Non-drug (test) order — resolves through the same
type→ handler → draft-order path, with its own field structure (e.g. concept, urgency, specimen). Adding a new order type = registering a handler; the order basket needs no type-specific changes. (INR test order.)A template whose
typehas no registered handler is excluded from search results (logged with the template's uuid and type) — never offered, never crashes the search; the orderable's plain blank-form entry remains available.
Dispensing defaults.
When a duration is present, quantity-to-dispense is auto-computed (dose-form-units × frequency/day × duration) and shown as editable. (Amoxicillin → 1 × 3 × 7 = 21 tablets.)
When quantity cannot be computed — PRN, chronic, or a free-text schedule — the template's explicit default
quantity/quantityUnits/numRefillspre-fills. (Paracetamol #100/0; Amlodipine #90/3; Metformin #180/3; Diazepam #12/0, summed across the taper.)Dose units and dispense units can differ (paracetamol dosed in mg, dispensed in tablets), and packaged/combination products dispense by pack or "dose" rather than by tablet — so
quantityUnitsis recorded on every order. (Multi-ingredient/combination products are out of the V1 seed set; see §6.3 and the deferred list.)
Implementer seeding.
Seeding is via the backend REST API (or SQL) for V1. Initializer-based loading needs a domain loader — a backend task, scoped into V1 explicitly or listed as a non-goal (see §9).
REST create/update/retire endpoints remain available for tooling.
A documented example payload set is provided (§6.5): four simple drug templates, one free-text drug template (diazepam), and one simple test order (Renal function panel)
5. Market Analysis
Major EMRs separate "the catalogue of orderables" from "pre-filled ways to order them," and none force a template to exist before an orderable can be ordered. The same pattern holds across order types — drugs and tests — which is the market evidence for a generic, not drug-specific, model.
Cerner — "order sentences." Pre-completed order details (dose/route/frequency/PRN) attached to an orderable, selectable in one click and editable at point of order. Providers can save a modified order as a personal favourite. Cerner order sentences exist for labs and other orderables too, not just drugs — direct support for the type-driven approach. Confirms pre-fill-not-restrict and one-orderable-many-sentences.
Epic — preference lists, favourites, SmartSets. Preference list = the browsable catalogue; Favourite = a single order saved with preferred dose/route/frequency/dispense (the per-order template analog, created by starring a real order); SmartSet / order set = a grouped bundle for a protocol. Two lessons: favourites are created from real orders ("star this order to reuse") — a strong argument for the eventual "save current order as template" authoring path; and Epic treats the single favourite and the multi-order set as points on one continuum, which is exactly the unify-on-order-sets option under discussion
FHIR —
ActivityDefinition→ a request. A reusable, patient-independent definition instantiated into a patient-specific request — precisely the template→order relationship, and it is not drug-specific:ActivityDefinitionseeds aMedicationRequest, aServiceRequest(labs/referrals), and others. Keeping our shape translatable toActivityDefinition(and the resulting orders to the matching request types) is the north star, even though V1 stores its own JSON.
Common threads: (1) template pre-fills, never gates; (2) one orderable → many templates; (3) templates span order types, not just drugs; (4) single templates and multi-order sets sit on one continuum; (5) templates can be institutional or personal (V1 = institutional only); (6) best authoring path is "save a real order as a template."
6. Technical Considerations & Dependencies
6.1 Architecture / dependencies
Template resolution — the core abstraction. Each template
type(versioned URI) routes to anOrderTemplateHandler<TemplateType>that (a) resolves the template into a draftOrderand (b) renders it as a human-readable string for search results and review. Adding a new order type (e.g. test orders) means registering a handler — the order basket needs no type-specific changes. This is what keeps the design generic rather than drug-shaped.Templating is a service, not a search feature (Burke, squad call). The handler interface — given an orderable/order and a template, produce (a) the draft order and (b) the rendering — is consumed identically by order search, order-set rendering, and future decision-support suggestions ("offer this order"). Search knows templates exist; templates know nothing about search. Consumers never assume a template's shape: they dispatch on its type, and an unregistered type behaves as no-template.
Storage / where the domain lives — Keep
openmrs-module-ordertemplatesfor V1; commit to a first-class coreorder_templatelater; do not model templates as single-member order sets — order sets are one consumer of templates viaorder_set_member.orderTemplate/orderTemplateType. The interfaces below (handler, resolution, rendering) are storage-agnostic, so the migration lands without reworking the frontend.Fetch / search API. V1 needs to resolve templates for an orderable and surface them in search. Preferred direction (Ian): a single backend endpoint that interleaves orderables and their templates server-side, so the frontend stops doing per-drug template fetches. The existing module's
?drug=/?concept=fetch works for a first increment but is the drug-keyed pattern we're moving away from.Orderable-set membership: a test-order template only surfaces if its concept is a member of the configured lab orderable concept set — seeding one without that makes it invisible in search.
Feature Flag: both the medications app and the tests app declare the
ordertemplatesoptional backend dependency in theirroutes.json; the flag auto-enables when the module is installed.Privilege:
View OrderTemplatesgates the template lines on drug and test search tiles and the pinned-orders template fetch; roles without it get safe blank-form degradation.Core deps:
webservices.rest;MedicationDispensefor the separate dispensing concern.Frontend: O3 order basket (
@openmrs/esm-patient-medications-app) + shared types in@openmrs/esm-patient-common-lib. The basket resolves templates via the handler, renders them in search results, applies defaults, and allows edit. No template → blank form.Metadata / seeding: V1 seeds via the backend REST API (or SQL). Initializer-based loading needs a domain loader — a separate backend task, scoped in or listed as a non-goal (§9). Provide sample REST seed payloads.
6.2 Coverage: assist and measure, don't enforce
V1 does not guarantee every searchable orderable has a template — and must not try to. A constraint requiring a template before an orderable can be ordered recreates the rejected common-medication.json coupling, and can't hold in practice (the catalogue and the template set are maintained independently). Instead:
Concept-level fallback is the primary coverage lever — coverage counted per concept, not per catalogue row.
Guaranteed blank-form fallback (hard requirement) — makes partial coverage acceptable.
Coverage / gap report — join the orderable/concept set against the template store, list uncovered orderables. Proposed scope: fast-follow — a pre-canned query/script, not an in-app screen (reporting doesn't belong in the order basket).
Seed-time pairing — pair a template CSV with the catalogue/formulary CSV via the Initializer, not a DB constraint.
6.3 Clinical Content Governance
Ownership: Each implementing site designates a clinical owner for template content (pharmacy for drug orders; the lab/clinical lead for test orders). OpenMRS does not ship a universal formulary of templates.
Lifecycle: Create → review → activate → retire. Retired templates must not be returned by the search the basket uses.
Naming (recommended): Include the orderable and its key defaults in the name (e.g. "Amoxicillin 500 mg PO q8h" for a drug; "Renal function panel — routine" for a test).
Coverage: Fast-follow canned script listing orderables/concepts without an active template. Not part of the order-basket UI.
6.4 Safety & Tech Risks
Stale defaults: A template whose dose no longer matches current formulary strength could suggest an unsafe order. Mitigation: retire on catalogue change; coverage script flags gaps; clinicians always review before sign; existing order validation still runs.
Wrong template selection: Several templates for one orderable increase mis-pick risk. Mitigation: clear, self-describing names; render defaults (and blanks) in the search result so the clinician sees exactly what they're adding before they add it.
Over-trust of pre-fill: Clinicians may sign without reading. Mitigation: no auto-sign; all fields visible and editable.
Non-drug order types (NEW): As test orders join in V1, their handler must apply the same safety posture — no auto-sign, editable, validated by the relevant order service — not just
DrugOrdervalidation.Patient-harm pathway: Incorrect order from a bad template. Residual risk accepted only with (a) clinical ownership of content, (b) a retire process, (c) no bypass of the relevant order-service validation.
V1 scope
Order templates, drug orders first — drug orders (Simple + Free-text dosing) and at least one non-drug order type (simple test order), to prove the
type-driven model works across order types rather than being drug-shaped.type→OrderTemplateHandlerresolution and rendering — each templatetyperesolves to a draft order and a human-readable string.Templates rendered in search results as the primary interaction (each ready-made way to order shown as its own result), with defaults filled and blanks shown for undefaulted fields.
System / implementation-level templates (shared, not per-user).
Order-basket pre-fill flow incl. concept-level fallback and guaranteed blank-form fallback (nothing is un-orderable for lack of a template).
Free-text SIG pre-fill.
Dispensing defaults optional on the template:
quantity/quantityUnits/numRefills/durationpre-fill when authored. When a duration is present and no explicit quantity is authored, quantity-to-dispense is auto-computed (dose-form-units × freq/day × duration) and shown editable.MedicationDispenseremains the source of truth for what is actually dispensed.Coverage gap report as a fast-follow canned query/script.
Storage / backend home: module for V1, core
order_templatelater — interfaces are storage-agnostic so the move needs no frontend rework.
6.5 V1 template seed set
Four simple drug templates, one free-text drug template, and one simple test order — chosen to exercise every part of the model: simple dosing, PRN, chronic + refills, free-text dosing, and a non-drug order type. Seeded indication values (Pain, Bacterial infection, Hypertension, Diabetes mellitus type 2) are pending clinical review; diazepam intentionally carries none.
Paracetamol — near-universal analgesic/antipyretic (PRN; explicit quantity, no duration)
1000 mg oral every 6 hours as needed for pain. #100, 0 refills
Amoxicillin — most-prescribed antibiotic; ARIs are the most common presenting complaint (clean acute; quantity computed from duration)
500 mg oral every 8 hours for 7 days. #21, 0 refills
Amlodipine — rising NCD / hypertension burden (chronic; concept-level "general default"; refills)
5 mg oral once daily. #90, 3 refills
Metformin — chronic-disease / diabetes management (chronic; multi-month supply; refills)
500 mg oral twice daily. #180, 3 refills
Diazepam — controlled substance (free-text dosing; changing-dose schedule)
Free-text reducing course: 5 mg three times a day for 2 days, then twice a day for 2 days, then at night for 2 days, then stop. #12, 0 refills
Renal function panel — baseline renal monitoring (non-drug order type; proves the type-driven model)
Test order, routine urgency. (No dosing — different field structure, same handler path; foreshadows the Hypertension order set deferred to a later increment.)
{ "type": "https://schema.openmrs.org/order/template/drug/simple/v1",
"dosingType": "org.openmrs.SimpleDosingInstructions",
"dosingInstructions": {
"dose": [{ "value": 1, "default": true }],
"unit": [{ "value": "tablet", "valueCoded": "<tablet-uuid>", "default": true }],
"route": [{ "value": "Oral", "valueCoded": "160240AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "default": true }],
"frequency": [{ "value": "Every 8 hours", "valueCoded": "<q8h-uuid>", "frequencyPerDay": 3, "default": true }],
"asNeeded": false,
"asNeededCondition": null,
"duration": [{ "value": 7, "default": true }],
"durationUnits": [{ "value": "Days", "valueCoded": "1072AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "default": true }],
"quantity": [{ "value": 21, "default": true }],
"quantityUnits": [{ "value": "tablet", "valueCoded": "<tablet-uuid>", "default": true }],
"numRefills": [{ "value": 0, "default": true }],
"instructions": [{ "value": "Complete the full 7-day course.", "default": true }] } }// Free-text dosing — Diazepam 5 mg reducing course, #12, 0 refills
// POST ws/rest/v1/ordertemplates/orderTemplate
{
"name": "Diazepam 5 mg — reducing course (free-text) — #12",
"description": "Starter: diazepam free-text reducing course, dispense 12, 0 refills",
"drug": "<diazepam-5mg-tablet-drug-uuid>",
"concept": "<diazepam-concept-uuid>",
"template": "{
\"type\": \"https://schema.openmrs.org/order/template/drug/freetext/v1\",
\"dosingType\": \"org.openmrs.FreeTextDosingInstructions\",
\"dosingInstructions\": {
\"instructions\": [{ \"text\": \"5 mg three times a day for 2 days, then twice a day for 2 days, then at night for 2 days, then stop.\", \"default\": true }],
\"quantity\": [{ \"value\": 12, \"default\": true }],
\"quantityUnits\": [{ \"value\": \"Tablet\", \"valueCoded\": \"1513AAAA…\", \"default\": true }],
\"numRefills\": [{ \"value\": 0, \"default\": true }]
}
}"
}// Simple test order — Renal function panel, routine urgency
// POST ws/rest/v1/ordertemplates/orderTemplate
{
"name": "Renal function panel — routine",
"description": "Starter: renal function panel routine test order",
"concept": "<renal-panel-concept-uuid>",
"template": "{
\"type\": \"https://schema.openmrs.org/order/template/test/simple/v1\",
\"orderType\": \"testorder\",
\"concept\": { \"value\": \"Renal function panel\", \"valueCoded\": \"<renal-panel-concept-uuid>\" },
\"urgency\": \"ROUTINE\",
\"instructions\": [{ \"value\": \"Fasting not required.\", \"default\": true }]
}"
}6.6 Backend Roadmap
V1 ships with no backend changes — it consumes the existing openmrs-module-ordertemplates as-is. The frontend's template fetch is deliberately confined to one file, so every stage below lands without reworking any screen.
Stage 1 — Move templates to their permanent home (ratified, §9 "Where does the domain live")
Migrate template storage from the standalone module to a first-class coreorder_template. Per the ratified position (Burke): templates are their own domain — do not model them as single-member order sets; order sets are one consumer of templates. The migration also adds what the module never had: server-side JSON validation on save (knowntypeURI + parseable body) and fixes the over-return bug (an unresolved?drug=/?concept=must return empty, never all rows). During transition, either keep the module's read endpoint answering from the new store for one release, or coordinate a same-release frontend seam swap.
Effort: ~1–2 weeks after a core design review. Owner: platform/backend.Stage 2 — Order-set REST readiness (§9 row)
Audit the existing/ws/rest/v1/ordersetCRUD (ships with the REST module, platform 1.12+), then close the ordering-workflow gaps: search-by-member-orderable (?orderable=<conceptUuid>), a representation exposing members with theirorderTemplatepayloads in one round trip, and retired-exclusion by default. Same over-return rule as Stage 1.
Effort: days. Owner: REST module. Independent of Stage 1; needed before Stage 4.Stage 3 — Interleaved orderable search (Ian's preferred direction, §9 row)
A singleemrapiendpoint —GET /ws/rest/v1/emrapi/orderable?q=…&types=…— returning drugs, tests, and order sets with their templates attached: scoped server-side, retired excluded, ranked and paginated. Privilege-degrading: a caller withoutView OrderTemplatesreceives the same orderables with empty template lists, not a 403, so search never breaks for non-privileged prescribers. Thetemplatefield stays a JSON string in today's stored shape, so the frontend consumes it unchanged; adopting the endpoint retires the current per-drug fetch pattern in a one-file frontend swap.
Effort: ~1–2 weeks backend + 1–2 days frontend. Owner: emrapi module.Stage 4 — First multi-orderable bundle: the Hypertension order set (§9 row; Future Iterations)
An order set whose members — a first-line antihypertensive (choose exactly one: amlodipine 5 mg default, hydrochlorothiazide 12.5 mg, enalapril 5 mg) and a baseline workup (choose any: renal function panel default, lipid profile, fasting blood glucose, urinalysis, ECG) — each carry their own template via the existing order_set_member.orderTemplate / orderTemplateType columns (Ian #44): this is precisely how order sets consume templates under the Stage-1 ruling. The frontend resolves each selected member through its registered handler into the drug and lab baskets. Selection UI per the squad-call ruling: adding a set opens its description and choice groups, and the basket fills only when the set is saved. Recurring-schedule semantics ("repeat the renal panel in 2 weeks") are explicitly out of scope and need their own design session (#46); the MVP carries any schedule as instruction text.
Effort: ~1–2 weeks frontend once Stages 1–2 exist. Blocked only on Stage 1.
Future Iterations
Unified orderable search — one search box in the order basket returning drugs, tests, templates, and order sets together, with order-type filters instead of separate search areas (Ian). The natural UI counterpart of the §6.6 Stage-3 interleaved endpoint; a bigger basket restructure, so post-V1.
Template authoring/management UI — hand-authored JSON/REST for V1; a "save this order as a template" path is a strong fast-follow.
Paired
{dose, doseUnits}schema reshape — keep the deployeddose[]+unit[]array shape for V1 (§9); revisit post-V1 (breaking change).Weight-based / pediatric dosing — mg/kg × weight, and dispensing in mL/bottles (Kanter #47).
Multi-order templates / order-set bundles — a template carrying more than one orderable (e.g., the Hypertension set — an antihypertensive plus its baseline workup. The type-driven model and the §9 storage choice are picked so this generalises cleanly; the bundle itself is deferred.
Personal favourites — a prescriber saves their own modified order.
Regimen reporting — patient on a named HIV/TB/oncology regimen linked to dispensed drugs; own design session (Kanter #46).
Order types beyond drug + test — referral and others; the
type/handler model hosts them later.