Best Practice: Linking Drug Forms to Dose Units/Routes via Concept Attributes

Hi OpenMRS Community, @ibacher @grace @dkayiwa

I am looking for some suggestions

We are working on improving our medication ordering workflow. Our goal is to make the UI more intuitive by automatically suggesting or filtering the relevant Dose Units and Routes of Administration based on the selected Drug Form.

Use Case Example:

  • If a clinician selects “Tablet” as the Drug Form, the Dose Unit field should ideally suggest options like “tablet”. and the Route field might default to or only show “Oral”.
  • If “Injection” is selected, Dose Units like “mg”, “ml” should be suggested, and Routes like “IV”, “IM”, “SC” should be available.

Proposed Metadata Solution:

We are thinking to manage these relationships using the OpenMRS metadata:

  1. Create Concept Sets (ConvSets) for relevant Dose Units and Routes grouped by Drug Form (e.g., a “Tablet Dose Units ConvSet”, an “Injectable Routes ConvSet”).
  2. Use Concept Attributes attached to the Drug Form concepts (e.g., the “Tablet” concept) to link them to these appropriate ConvSets.

Implementation Options We’re Considering:

We’ve are thinking of below ways to implement the Concept Attribute linking:

Option 1: Specific Attributes per Relationship

  • Define distinct Concept Attribute Types for each type of link.
    • Example Attribute Type 1:
      • Name: Relevant Dose Unit Set
      • Datatype: org.openmrs.customdatatype.datatype.ConceptDatatype
      • Description: Links a Drug Form concept to the ConvSet containing its appropriate dose units.
    • Example Attribute Type 2:
      • Name: Relevant Route Set
      • Datatype: org.openmrs.customdatatype.datatype.ConceptDatatype
      • Description: Links a Drug Form concept to the ConvSet containing its appropriate administration routes.
  • How it works: The ‘Tablet’ concept would have an attribute of type ‘Relevant Dose Unit Set’ whose value is the UUID of the “Tablet Dose Units ConvSet”.
  • Cons: Might be cluttering, as it Requires creating multiple specific attribute types for different kinds of links.

Option 2: Generic “Extension” Attribute (Our Current Leaning)

  • Define one single, generic Concept Attribute Type.
    • Example Attribute Type:
      • Name: Concept Extension
      • Datatype: org.openmrs.customdatatype.datatype.FreeTextDatatype
      • Description: Generic attribute for extending concepts with additional data points (potentially structured, like JSON).
  • How it works: For a Drug Form concept like ‘Tablet’, we would store a JSON string within the value of this single attribute type:
    {
      "doseUnitSet": "a8c7c53d-c3bf-4b33-8f0c-8e5e3b5c9c7d", // UUID of Tablet Dose Units ConvSet
      "routeSet": "ROUTE_CONVSET_UUID_FOR_TABLET" // UUID of relevant Route ConvSet
    }
    
  • Rationale: The main motivation for this approach is to avoid creating numerous specific attribute types. We envision potentially using this same “Concept Extension” attribute on other types of concepts (e.g., Diagnoses, Procedures) in the future to store different kinds of extension data (maybe different JSON structures).
  • Pros: Minimizes the number of Concept Attribute Types to manage, highly flexible.
  • Cons: Requires application logic to parse the JSON, loses semantic clarity in the metadata (the attribute name is generic), harder data validation and querying based on the JSON content.

Our Questions for the Community:

  1. Is the Generic Attribute approach (Option 2), using FreeText + JSON within a single attribute type for various purposes, a viable or advisable strategy within the OpenMRS ecosystem, considering the trade-offs (especially parsing complexity)?
  2. Is the Specific Attribute approach (Option 1) generally considered the best practice for this kind of structured linking requirement?
  3. How have other implementations typically solved this need to link drug forms to appropriate dose units and routes? Are there common patterns or alternative approaches (e.g., UI config files, different metadata structures) we should consider?
  4. What potential pitfalls or long-term maintenance issues should we be aware of with either approach?

We appreciate any insights, experiences, or recommendations you can share! Thanks in advance ! Horaira

1 Like

Also in same way, can i add attribute to generic name to add additional information like reactions , side effects , specially like this , so that provider could see and prescribe accordingly? Or there is other recommended way and this could be not right way ?

Hi @dkayiwa, @ibacher, @pwargulak @mogoodrich @wyclif @mseaton

Sorry for tagging, as i am looking for some suggestions here. I tried to find some active members and tagged. Thanks in advance for any help!

Further tagging Bahmni team @angshuonline @gsluthra1 @gsluthra

Little bit confused if this question makes any sense or it’s very obvious and I am not able to grasp it, might be I have not dig down enough in documentation and already there is some solution, at least that feedback would be helpful :slight_smile:

While further searching I found some discussion related to order template, not sure if that would fulfill my requirement Drug Orders: Order Templates modelling - Software / Platform - OpenMRS Talk

2022-06-13 TAC: Order Templates to Support Drug Orders - Development - OpenMRS Talk

One more dumb question, if the drug (Drug Concepts) already has option for strength, drug form then why not it should have additional field for default route?

On further exploring, instead of having it as attribute, i am thinking to create a concept with FreeText Datatype, where i can put the linking

{
  "DrugForms": {
    "Tablet": {
      "defaultDoseUnit": [
        { "uuid": "TabletUUID", "default": true },
        { "uuid": "OtherDoseUnitUUID" }
      ],
      "defaultRouteUnit": [
        { "uuid": "OralUUID", "default": true },
        { "uuid": "SublingualUUID" }
      ]
    },
    "Capsule": {
      "defaultDoseUnit": [
        { "uuid": "CapsuleUUID", "default": true },
        { "uuid": "OtherDoseUnitUUID" }
      ],
      "defaultRouteUnit": [
        { "uuid": "OralUUID", "default": true }
      ]
    },
    "Injection": {
      "defaultDoseUnit": [
        { "uuid": "ml_UUID", "default": true },
        { "uuid": "ampule_UUID" },
        { "uuid": "IU_UUID" }
      ],
      "defaultRouteUnit": [
        { "uuid": "IM_UUID", "default": true },
        { "uuid": "IV_UUID" }
      ]
    },
    "Syrup": {
      "defaultDoseUnit": [
        { "uuid": "ml_UUID", "default": true },
        { "uuid": "teaspoon_UUID" }
      ],
      "defaultRouteUnit": [
        { "uuid": "OralUUID", "default": true }
      ]
    }
  }
}

Any suggestion?

Are you planning to do this in O3 with a custom esm? And then potentially a backend module for some custom server side functionality?

It would also be good to consider how referencing the sets/concepts could be moved away from UUIDs which would be server specific, to something that can be standardized and shared. Either a CIEL ID, or find an appropriate HL7 or SNOMED reference.

Thanks @dkayiwa for responding , and sorry as was not able to reply earlier Yes and no, Yes for custom esm module , but in backend till now i am thinking to achieve through configuration, Here is the revised approach i am looking for by default we are thinking to place this configuration in globalproperties(bahmni_config/masterdata/configuration/globalproperties/)

and if user want to further customize (we would be providing UI using custom esm) the customized one will be placed in the provider attribute (freetext, same json file with customized field) So esm will check if provider specific configuration is there, if yes use it if not then use global one , here is a sample default configuration will look like

    <globalProperty>
      <property>medication.form.defaultSettings</property>
      <value><![CDATA[{
  "name": "Default Medication Settings",
  "description": "Default configuration for medication form display, units and mappings",
  "value": {
    "uiPreferences": {
      "columns": {
        "index": { 
          "show": true, 
          "showInPrint": true, 
          "order": 1,
          "label": "#"
        },
        "dosageForm": { 
          "show": true, 
          "showInPrint": true, 
          "order": 2,
          "label": "Form"
        },
        "drug": { 
          "show": true, 
          "showInPrint": true, 
          "order": 3,
          "required": true,
          "label": "Medication"
        },
        "generic": { 
          "show": true, 
          "showInPrint": true, 
          "order": 4,
          "label": "Generic Name"
        },
        "doseWithUnits": { 
          "show": true, 
          "showInPrint": true, 
          "order": 5,
          "required": true,
          "label": "Dose"
        },
        "frequency": { 
          "show": true, 
          "showInPrint": true, 
          "order": 6,
          "required": true,
          "label": "Frequency"
        },
        "durationWithUnits": { 
          "show": true, 
          "showInPrint": true, 
          "order": 7,
          "required": true,
          "label": "Duration"
        },
        "quantity": { 
          "show": true, 
          "showInPrint": true, 
          "order": 8,
          "label": "Quantity"
        },
        "status": { 
          "show": true, 
          "showInPrint": false, 
          "order": 9,
          "label": "Status"
        },
        "instructions": { 
          "show": true, 
          "showInPrint": true, 
          "order": 10,
          "label": "Instructions"
        },
        "startDate": { 
          "show": true, 
          "showInPrint": true, 
          "order": 11,
          "label": "Start Date"
        }
      },
      "language": {
        "primary": "en",
        "additional": "hi",
        "fields": {
          "drug": true,
          "dosageForm": true,
          "doseUnits": true,
          "frequency": true,
          "instructions": true
        }
      },
      "inputStyles": {
        "inline": { "enabled": true, "default": true },
        "multiRow": { "enabled": true, "default": false }
      }
    },
    "drugConfiguration": {
      "drugForms": {
        "conceptSetUuid": "162350AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
        "name": "Drug forms",
        "options": [
          {
            "uuid": "1513AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
            "name": "Tablet(s)",
            "order": 1
          },
          {
            "uuid": "1608AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
            "name": "Cap(s)",
            "order": 2
          },
          {
            "uuid": "1515AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
            "name": "Syrup",
            "order": 3
          },
          {
            "uuid": "9bb0795c-4ff0-0305-1990-000000000034",
            "name": "Injection",
            "order": 5
          },
          {
            "uuid": "162413AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
            "name": "Cream",
            "order": 6
          },
          {
            "uuid": "162427AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
            "name": "Lotion",
            "order": 7
          }
        ]
      },
      "doseUnits": {
        "conceptSetUuid": "bb03882f-f496-11ed-b02c-0242ac150003",
        "name": "Dosing Units",
        "options": [
          {
            "uuid": "bb07f339-f496-11ed-b02c-0242ac150003",
            "name": "mg",
            "order": 1
          },
          {
            "uuid": "bb0786b6-f496-11ed-b02c-0242ac150003",
            "name": "ml",
            "order": 2
          },
          {
            "uuid": "bbba5dec-f496-11ed-b02c-0242ac150003",
            "name": "IU",
            "order": 3
          },
          {
            "uuid": "bb070849-f496-11ed-b02c-0242ac150003",
            "name": "Drop",
            "order": 4
          },
          {
            "uuid": "bc318bc6-f496-11ed-b02c-0242ac150003",
            "name": "Tablet",
            "order": 5
          },
          {
            "uuid": "bc321814-f496-11ed-b02c-0242ac150003",
            "name": "Capsule",
            "order": 6
          },
          {
            "uuid": "bbbae4b0-f496-11ed-b02c-0242ac150003",
            "name": "Unit(s)",
            "order": 7
          }
        ]
      },
      "routes": {
        "conceptSetUuid": "bb0bde7e-f496-11ed-b02c-0242ac150003",
        "name": "Drug Routes",
        "options": [
          {
            "uuid": "bb0d7c23-f496-11ed-b02c-0242ac150003",
            "name": "Oral",
            "order": 1
          },
          {
            "uuid": "bb0c43bc-f496-11ed-b02c-0242ac150003",
            "name": "Intramuscular",
            "order": 2
          },
          {
            "uuid": "bb0cfea4-f496-11ed-b02c-0242ac150003",
            "name": "Intravenous",
            "order": 3
          },
          {
            "uuid": "bc332605-f496-11ed-b02c-0242ac150003",
            "name": "Nasal",
            "order": 4
          },
          {
            "uuid": "bc329e67-f496-11ed-b02c-0242ac150003",
            "name": "Topical",
            "order": 7
          },
          {
            "uuid": "bc33aa98-f496-11ed-b02c-0242ac150003",
            "name": "Inhalation",
            "order": 8
          },
          {
            "uuid": "bb0ea60e-f496-11ed-b02c-0242ac150003",
            "name": "Sub Cutaneous",
            "order": 9
          }
        ]
      },
      "frequency": {
        "conceptSetUuid": "160855AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
        "name": "Medication frequency",
        "displayStyles": {
          "coded": {
            "default": true,
            "options": [
              {
                "uuid": "160862AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
                "name": "Once daily",
                "order": 1
              },
              {
                "uuid": "160858AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
                "name": "Twice daily",
                "order": 2
              },
              {
                "uuid": "160866AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
                "name": "Thrice daily",
                "order": 3
              },
              {
                "uuid": "160870AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
                "name": "Four times daily",
                "order": 4
              },
              {
                "uuid": "160857AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
                "name": "On an as needed basis",
                "order": 5
              },
              {
                "uuid": "160863AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
                "name": "Once daily, at bedtime",
                "order": 6
              },
              {
                "uuid": "160865AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
                "name": "Once daily, in the morning",
                "order": 7
              }
            ]
          }
        }
      },
      "mappings": {
        "TABLET": {
          "uuid": "1513AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
          "name": "Tablet(s)",
          "doseUnits": [
            {
              "uuid": "bc318bc6-f496-11ed-b02c-0242ac150003",
              "name": "Tablet",
              "default": true
            }
          ],
          "routes": [
            {
              "uuid": "bb0d7c23-f496-11ed-b02c-0242ac150003",
              "name": "Oral",
              "default": true
            }
          ]
        },
        "CAPS": {
          "uuid": "1608AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
          "name": "Cap(s)",
          "doseUnits": [
            {
              "uuid": "bc321814-f496-11ed-b02c-0242ac150003",
              "name": "Capsule",
              "default": true
            }
          ],
          "routes": [
            {
              "uuid": "bb0d7c23-f496-11ed-b02c-0242ac150003",
              "name": "Oral",
              "default": true
            }
          ]
        },
        "SYRUP": {
          "uuid": "1515AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
          "name": "Syrup",
          "doseUnits": [
            {
              "uuid": "bb0786b6-f496-11ed-b02c-0242ac150003",
              "name": "ml",
              "default": true
            }
          ],
          "routes": [
            {
              "uuid": "bb0d7c23-f496-11ed-b02c-0242ac150003",
              "name": "Oral",
              "default": true
            }
          ]
        },
        "CHEWABLE": {
          "uuid": "162412AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
          "name": "Chewable tablet",
          "doseUnits": [
            {
              "uuid": "bc318bc6-f496-11ed-b02c-0242ac150003",
              "name": "Tablet",
              "default": true
            }
          ],
          "routes": [
            {
              "uuid": "bb0d7c23-f496-11ed-b02c-0242ac150003",
              "name": "Oral",
              "default": true
            }
          ]
        },
        "INJECTION": {
          "uuid": "9bb0795c-4ff0-0305-1990-000000000034",
          "name": "Injection",
          "doseUnits": [
            {
              "uuid": "bb0786b6-f496-11ed-b02c-0242ac150003",
              "name": "ml",
              "default": true
            }
          ],
          "routes": [
            {
              "uuid": "bb0c43bc-f496-11ed-b02c-0242ac150003",
              "name": "Intramuscular",
              "default": true
            }
          ]
        },
        "CREAM": {
          "uuid": "162413AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
          "name": "Cream",
          "doseUnits": [
            {
              "uuid": "bbbae4b0-f496-11ed-b02c-0242ac150003",
              "name": "Unit(s)",
              "default": true
            }
          ],
          "routes": [
            {
              "uuid": "bc329e67-f496-11ed-b02c-0242ac150003",
              "name": "Topical",
              "default": true
            }
          ]
        },
        "LOTION": {
          "uuid": "162427AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
          "name": "Lotion",
          "doseUnits": [
            {
              "uuid": "bbbae4b0-f496-11ed-b02c-0242ac150003",
              "name": "Unit(s)",
              "default": true
            }
          ],
          "routes": [
            {
              "uuid": "bc329e67-f496-11ed-b02c-0242ac150003",
              "name": "Topical",
              "default": true
            }
          ]
        }
      }
    }
  }
}]]></value>
      <description>Default configuration for medication form including display preferences and mappings</description>
    </globalProperty>

I am looking for input, if it is recommended way to do or any other way to achieve, anyone has tried like this ?

@akanter thanks for the input, do you mean to say that I should avoid using uuid instead using CIEL ID or appropriate HL7 or SNOMED reference?

We are trying to get away from using server-specific knowledge references such as UUIDs if the reference is intended to be shared or re-used on different servers. That means looking for a standard code that does not change based on how the concept got into the database. CIEL IDs are good (if there are CIEL maps) but CIEL is not an international standard the same way that SNOMED is. If there are standard codes from SNOMED for a drug form, than that would be good (but that also assumes that people have SNOMED codes on their concepts). CIEL provides SNOMED maps, and we hope that CIEL will be pretty standard through the OpenMRS community, but those are the type of questions you should be thinking about.

1 Like

Thanks @akanter, I agree.

One question generally comes in my mind, maybe I haven’t looked hard to understand it. In Ideal scenario we try to initialize openMRS with concepts which comes from CIEL or any other source and recommended way to create and manage using OCL.

And I see in O3 frontend we hardcoded UUID’s to fetch relevant things, as without right UUID’s it will not work at all (at least functional perspective). That makes CIEL or other dictionary or the concepts we are managing is single source of truth.

So, question I have, why not frontend also retrieve UUID’s from OCL or frontend can ask backend that give me reference UUID’s, not sure if I am understandable here or making any sense here or something like this is happening already.

It depends on how you create the concepts in your database. OCL does persist the UUIDs (external IDs) so it is possible that the concepts will not have their UUIDs change. However, if you clone the concept into your dictionary rather than create a reference to the CIEL source, then it will receive a different UUID. Since it is possible that an implementer wanted to make a change to the CIEL concept that required a server-saved concept with a new UUID, it would not be preferred to lose the link between that concept and an IG, or rule. So, if the reference was made using a “SAME-AS” mapping to a standard code, then it wouldn’t change regardless of the UUID, etc. That is the reason.

1 Like