Clinical Decision Support

This is a very early work-in-progress with the intention of eliciting a full design spec for the decision support infrastructure of OpenMRS. Please add, edit, and abuse this page!

Definitions

  • Arden Service : a set of services to provide a sophisticated logic engine to OpenMRS (Thank you Vibha!)

  • Arden Rule : a text-based MLM

  • Arden Class : a Java class either generated from automatic or manual translation of an Arden Rule

  • Arden Clause : an expression with optional aggregate function(s) and criteria (Arden Syntax's curlicue bracket...perhaps including the expression around it) — e.g., "LAST POTASSIUM GREATER THAN 4.5"

  • Arden Value : an result object returned by an Arden Class (see details below)

  • Arden Data Source : a Java class implementing the ArdenDataSource interface and capable of converting an Arden Clause into an ArdenValue.

Arden Service

The Arden Service serves as a Arden Class Factory of sorts. The Arden Service provides the following services to OpenMRS:

  1. The ability to translate an Arden Rule into a Arden Class

    • This entails taking a String (presumably in MLM format) and translating it into a Java class

    • We'll probably want to add some convenience methods that auto-generate some of the portions of an MLM that lend themselves to auto-generation — e.g., a method that, given the names/types of the desired return values, would generate the tail end of an MLM that places those values (or variable references) into the proper format to return an Arden Value object.

  2. Organizing and maintaining Arden Classes

    • I should be able to call the Arden Service from part of the OpenMRS core or from a new module and not have to worry about where or how the Arden Class files are managed.

    • This requires that the Arden Service generate a token or follow a specific naming scheme to retrieve & update Arden Classes

  3. Providing a simple means for evaluating Arden Classes

    • By passing a token (e.g., the name of a rule) to the service, I should be able to get back an Arden Value. For example, assuming the context and a target patient are provided, we would like to perform things like eval("GENDER"), eval("ASTHMATIC").asBoolean(), and eval("CREATININE").last()

Basically, we would like to be able to add Arden functionality at any point within the API, application, or modules. In it's simplest form, this means calling the Arden Service with a token (+ context & patient) and coercing the result to a string — e.g., allowing a web page to display the current patient's HIV status with JSP like HIV status: <?openmrs:eval name="HIV STATUS"?> which calls a taglib that is merely a wrapper to pull the current patient and context from the HTTP session and call something like context.getArdenService().eval(patient, "HIV STATUS").

Alternatively, any part of the application could achieve a higher level of functionality by merely persisting a block of text. Whenever the text changes, it would be passed to the Arden Service to be compiled into an Arden Class.

Additional functions for the Arden Service:

  • Return a list all available Arden Rule names

Arden Rule

Use Cases

Once these Arden rules are developed and available through the service, they can be used in a few ways:

Derived Concepts

Concepts with the datatype "rule" will have a corresponding Arden Class, generated from Arden Syntax within the concept definition (we may build these manually at first). When the value for a derived concept is requested through the API, the corresponding Arden Class will be executed and the result returned.

Ways to use derived concepts:

  • To derive a column in an export

  • As part of a filter in defining a patient cohort

  • Called from within another rule

  • Used to dynamically render part of a web page — e.g., to show the patient's HIV status on a web page

Clinical Reminder

Other Arden Classes are needed by the general milieu of decision support infrastructure as the substrate for clinical reminders or specific bits of text for a report or form. These classes are not inherently linked to concepts, but are accessible through the same service as derived concepts.

Ways to use clinical reminder rules:

  • In a Decision Support module

  • As string generation mechanisms for reports or forms

Form and Report Elements

Some Arden Classes will be used for generating parts of forms and reports. These Arden Classes may not always be linked to an OpenMRS concept.

  • generate a prompt for a question that uses "him" or "her" based on the patient's gender

  • generate a dynamic default for a specific input field

  • display a dynamic phrase based on patient-specific data within a report (e.g., compute the patient's age within a report header)

Arden Class

Translation of an Arden Rule into an Arden Class is one of the primary features of the Arden Service. Until the Arden Service is fully realized, we could manually write these Arden Classes.

Arden Class must follow a particular design pragma by extending the abstract ArdenRule class.

Constructor

An Arden Class should be constructed with references to an OpenMRS context, a patient, and an Arden Data Source.

  • The context allows for security and access to parts of the API.

  • The patient defines the ''current'' patient from whose record all data requests are performed

  • The data source implements an API that provides all the methods needed to retrieve data from the system (the Arden Class makes all data request through this data source)

''(Just thinking here...maybe we should construct the Arden Class with only the data source, letting the data source control provide the context and/or patient to the Arden Class if needed. I suspect that Arden Classes will primarily make requests to the data source, passing the patient and context. Will Arden Classes need to access other parts of the API? -@Burke Mamlin)''

Attributes

  • obsMap: A map of all the obs that created this DSS Object. The key is the Concept (for example - "birthWt")and the value is the OpenMRS Obs object

  • concludeVal: Does this mlm concluded? The object holds a conclude value (true or false). This qualifies the above obsMap

  • printStr: String representation of the mlm including any variable substitution such as name, gender etc.

  • context: OpenMRS context

  • locale: OpenMRS locale ''(the locale is specified within the context -@Burke Mamlin)''

  • patient: patient this DSS object belongs to

Methods

  • getString: If conclude value is true, returns the string

  • getConcludeVal: If concluded - true or false. This would allow in chaining of rules.

  • getAnalysis: Currently prints the value of the obsMap, why the object was created.

Arden Clause

The Arden Clause represents the data needed from the repository to perform a particular calculation. This is equivalent to Arden Syntax's read statement (curlicue bracket along with the predicates around the curlicue brackets).

  • "LATEST{CREATININE} WHERE IT IS GREATER THAN 4.5"

  • "MINIMUM{CD4 COUNT} WHERE IT IS WITHIN ONE YEAR"

We would like to extend this to allow for:

  • "CREATININE" (all creatinine results for a patient)

  • "LATEST{CARDIOVASCULAR RISK FACTOR}" (most recent value returned by cardiovascular risk factor rule)

Arden Clauses are represented by ArdenClause classes. Clauses can be constructed from concepts, rules, or other clauses. They also expose aggregate and predicate methods such as ''earliest()'', ''latest()'', ''max()'', and ''greaterThan ''. Every method returns another ArdenClause. The '''Arden Clauses do not actually fetch data or return values'''; rather, they represent a semantic hierarchy needed to resolve a particular Arden Value. The criteria are abstracted so an Arden Clause can be passed down to the database layer and, ideally, be translated to SQL or similar language.

For example, if an Arden Rule wants to set:

LAST_ELEVATED_CREATININE := LATEST {SERUM CREATININE} WHERE IT IS GREATER THAN 4.5;

this would be translated into:

private ArdenValue lastElevatedCreatinine; private ArdenValue lastElevatedCreatinine() { if (lastElevatedCreatinine == null) { Concept c = new Concept();  c.setId(790); // SERUM CREATININE //  lastElevatedCreatinine = ardenDataSource.eval((ArdenClause.concept(c).greaterThan(4.5)).latest());  lastElevatedCreatinine = dataSource.eval(patient, Aggregation.last(3), c, DateCriteria.within(Duration.days(365)));  } return lastElevatedCreatinine; }

and, if the rule simply returned this value, a web page could print the date of the last elevated creatinine with something like:

Last abnormally high creatinine was on context.getArdenService().eval(patient, "LAST ELEVATED CREATININE").toDate();

The Result (i.e., ArdenValue) object serves as a query result in Arden and a java.util.List of Results (basically time-value pairs) for external calls.

We might end up making ArdenValue an extension of Result that adds Arden-specific functions to it.

Arden Value

The return value for ''all'' Arden Classes (whether as derived concepts, clinical reminders, form elements, etc.) is an Arden Value object. By using a standard return object (fitting various needs into that format), we increase the flexibility of the system, because any part of the application that uses an Arden Rule will know what to expect in return.

The Arden Value should...

  • similar to our Obs objects, allow for a datetime value paired with a various representations of the value itself (as a code, a number, or a date).

  • gracefully act as one value or a set of values — i.e., return arrays or sets for any non-aggregate methods and provide methods for obtaining a single value if needed (we will need to come up with the algorithm for coercing a set into a single value appropriately)

  • expose public methods to get the value as String, numeric (Double), a Boolean, or Date.

The Arden Value object consists of obs (1..n OpenMRS objects - specifically the obs that led to the conclusion of mlm being true or false, we do not want the obs that did not contribute to the conclusion of this mlm?). These are the obs that we would want to hold on to for further use in derived concept when the level of decision making is passed a level up. For example in the lowBirthWtCalculated example (a derived concept consisting of - birthWt < 2500 gms or "parent reported" low birth weight), the derived concept "lowBirthWtCalculated" is attributed to say for example - "parent reported", then the obs associated with Arden Value object is "parent reported" and we weed out the other obs (in this case "birthWt"), otherwise we are just going to pass up the "if then" logic of the child Arden Class up to the derived concept. ''(we'll need to discuss this part...not sure I follow -[archive:@Former user (Deleted)])

Arden Data Source

Any class implementing the ArdenDataSource interface that provides the ability to transform an Arden Clause (a data request) into an Arden Value.

In most cases (e.g., for decision support), we are focused on a single patient and calculating derived concepts or form/report elements specific to an invidual patient. In these cases, the default Arden Data Source would be performing Obs queries or passing tokens back to the Arden Service to calculate other Arden Rules for the patient. However, when performing Arden Rules for a pre-defined cohort of patients, we will want to be able to fetch the data for patients in bulk and ''then'' apply the rules. This demands two features: (1) that Arden Classes be able to report their dependencies (so we can gather a list of all data elements needed for all rules before fetcing the data from the repository) and (2) that Arden Classes call an Arden Data Source to fetch needed data rather than going directly to the API (allowing us to substitute a cohort-sensitive data source that fetches data from a pre-fetched data set rather than going to the API).

Use Case

Each Arden Class returns an Arden Value object associated with a patient. One Arden Class can call another (rule chaining) by using the call syntax (in Arden) which results in a chain of the Arden Value objects. More to follow...

Resources