Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Wiki Markup
h2. What are rules?

In the context of [logic service|http://archive.openmrs.org/wiki/Logic_Service_Project],  a Rule is a piece of business logic used to mold the data received from  various data sources and present them in a desired way to the logic  service user. This frees the user of performing complex calculations on  data and moves that functionality into a single container - the Rule.

Think, for example, of a situation where a user wants to  calculate a person's age based on data retrieved from the PersonService.  A typical solution would be to retrieve the birthdate and then use Java  (or whatever language the user is accessing OpenMRS from) to calculate  age. Now if another user also needs to calculate a person's age, but  from a different point in code, the functionality would have to be  re-implemented. Of course, this is just a simple example, but you can  imagine how the amount of duplicated code (some of it probably buggy  and/or outdated) would grow for some more complex demands.

This HOWTO presents a sample rule that calculates if a patient is  HIV positive. Not all concepts used in this example are valid HIV  indicators, but they are used for demonstration's sake. Hopefully this  example should be enough for the interested reader to understand how  Rules work and encourage him/her to start writing new Rules.

\[[edit|http://archive.openmrs.org/index.php?title=Rule_HOWTO&action=edit&section=2]\]

h2. HIVPositiveRule

This Rule returns the date for which supporting data indicate the  patient is HIV positive, otherwise nothing. This is how we will define  the criteria:
*  *firstHivDiagnosis* = the first [PROBLEM ADDED|http://demo.openmrs.org/openmrs/dictionary/concept.htm?conceptId=6042] answered with any of the following concepts:
**  [HUMAN IMMUNODEFICIENCY VIRUS|http://demo.openmrs.org/openmrs/dictionary/concept.htm?conceptId=884]
**  [HIV INFECTED|http://demo.openmrs.org/openmrs/dictionary/concept.htm?conceptId=1169]
**  [ASYMPTOMATIC HIV INFECTION|http://demo.openmrs.org/openmrs/dictionary/concept.htm?conceptId=5327]

*  *viralLoad* = first [HIV VIRAL LOAD|http://demo.openmrs.org/openmrs/dictionary/concept.htm?conceptId=856]

*  *viralLoadQual* = first [HIV VIRAL LOAD, QUALITATIVE|http://demo.openmrs.org/openmrs/dictionary/concept.htm?conceptId=1305]

*  *cd4Count* = first [CD4 COUNT|http://demo.openmrs.org/openmrs/dictionary/concept.htm?conceptId=5497] < 200 (not a valid diagnostic criteria, but useful for our example)

*  *age* = age in months calculated from patient's birthdate (again, using age in this to bring in demographic information)

*  *result* = first among *firstHivDiagnosis*, *viralLoad*, *viralLoadQual*, *cd4Count* iff *age* > 1 year

We want this business logic in a single place, so people can use it no  matter what their access points into OpenMRS are. Thus, we create an  HIVPositiveRule class and implement the Rule interface defined in _org.openmrs.logic.rule.Rule_:package org.openmrs.logic.rule;

public class HIVPositiveRule implements Rule {
The central point of our implementation is the eval() method - it  takes a patient and the criteria as arguments. Basically this is the  place that will contain the business logic defined above. So let's see  what it looks like, step-by-step:public Result eval(Patient patient, LogicCriteria criteria) {

       Result allDiagnoses = Result.nullResult();
The allDiagnoses result is used for keeping all the results from  individual data lookups below. It is initialized by using a static  Result.nullResult() method, to indicate to the logic service it is  actually just a placeholder for future results.       try {           Boolean ageOK = Context.getLogicService().eval(patient,                   new LogicCriteria("AGE").gt(1)).toBoolean();           if (!ageOK)               return Result.nullResult();As defined earlier, our criteria for HIV positive can only be  fulfilled if and only if the patient is older than 1. Thus, we use the  Logic Service to do the thinking for us by supplying it this criteria as _new LogicCriteria("AGE").gt(1).toBoolean()_. Simple enough. What  this call actually does is it invokes the AgeRule behind the scenes,  which does the actual birthdate retrieval (via DemographicsRule) and age  calculation. Although we could code our _age > 1_ condition here, it is best left to the Logic Service, since it transforms the request into a much faster database query.
When we apply the _gt(1)_ criteria to our "AGE" token, the  actual result (patient's age) is returned only if the patient's data  matches the criteria. Otherwise, the result returned is a null result.  When a non-null result is coerced into a Boolean, the resulting boolean  value is _true_. Null results are coerced into _false_.           // we find the first HIV diagnosis           allDiagnoses.add(Context.getLogicService().eval(                   patient,                   new LogicCriteria("PROBLEM ADDED").contains(                           "HUMAN IMMUNODEFICIENCY VIRUS").first()));           allDiagnoses.add(Context.getLogicService().eval(                   patient,                   new LogicCriteria("PROBLEM ADDED").contains("HIV INFECTED")                           .first()));           allDiagnoses.add(Context.getLogicService().eval(                   patient,                   new LogicCriteria("PROBLEM ADDED").contains(                           "ASYMPTOMATIC HIV INFECTION").first()));This is where we begin our actual data retrievals. (All these _eval(...)_ calls are actually calling ObservationRule's _eval()_ method behind the scenes.) Since the criteria we seek deals with coded results, we need to use the _contains()_ operator. The mechanism behind the _new LogicCriteria("PROBLEM ADDED").contains("HUMAN IMMUNODEFICIENCY VIRUS").first()_ call is:# The obs table is looked up for the given patient, using as filter the [PROBLEM ADDED|http://demo.openmrs.org/openmrs/dictionary/concept.htm?conceptId=6042] concept# The list of observations is then filtered to match the value_coded=884, which is the concept ID for [HUMAN IMMUNODEFICIENCY VIRUS|http://demo.openmrs.org/openmrs/dictionary/concept.htm?conceptId=884]# The result would then normally be returned to the user,  listing all observations that match the criteria. But, since there is a _.first()_ method attached to the end of the criteria, only the earliest result (sorted by _date_created_) is returned.
Again, if there are no observations that match the given criteria, a null result is returned.           // first viral load           allDiagnoses.add(Context.getLogicService().eval(patient,                   new LogicCriteria("HIV VIRAL LOAD").first()));
           // first qualitative viral load           allDiagnoses.add(Context.getLogicService().eval(patient,                   new LogicCriteria("HIV VIRAL LOAD, QUALITATIVE").first()));
           // first CD4 COUNT < 200           allDiagnoses.add(Context.getLogicService().eval(patient,                   new LogicCriteria("CD4 COUNT").lt(200).first()));Next, we fetch more observation results, this time with no need for the _contains()_ operator, since these are trivial lookups. The _first()_ method is used in the same fashion, returning the first result by date (if any).           return allDiagnoses.getFirstByDate();
       } catch (LogicException e) {           return Result.nullResult();       }
   }
Once we have all our results fetched and stored inside the _allDiagnoses_ result, we use _allDiagnoses.getFirstByDate()_ to fetch the first event leading to an HIV positive diagnosis, and  return it. If there were none (or if there was an exception at some  point), a null result is returned.

\[[edit|http://archive.openmrs.org/index.php?title=Rule_HOWTO&action=edit&section=3]\]

h2. Registering a Rule with Logic Service

Now that we have a [functioning HIVPositiveRule|http://dev.openmrs.org/browser/openmrs/branches/logic/src/api/org/openmrs/logic/rule/HIVPositiveRule.java],  we need to make it public, so other people can use it. A Rule needs to  be registered with the Logic Service, and supply to it the tokens that  it "understands" - in this case, the "HIV POSITIVE" token.

At the moment of writing of this document, this registering  mechanism is still not perfected. Interested readers are invited to look  at our [RuleFactory|http://dev.openmrs.org/browser/openmrs/branches/logic/src/api/org/openmrs/logic/RuleFactory.java] and [LogicServiceImpl|http://dev.openmrs.org/browser/openmrs/branches/logic/src/api/org/openmrs/logic/impl/LogicServiceImpl.java] implementations and track the changes. As soon as the mechanism is established, this section will be updated.

\[[edit|http://archive.openmrs.org/index.php?title=Rule_HOWTO&action=edit&section=4]\]

h2. Usage

Once the rule is registered with the Logic Service, one can use standard Logic Service _eval()_ calls to invoke a Rule and test its functionality. We can test the HIVPositiveRule by using:PatientSet patients = ... // initialize a cohort
Map<Integer, Result> hivPositive = Context.getLogicService().eval(patient, "HIV POSITIVE");
After the call returns, the _hivPositive_ map would be populated  with results, grouped by each HIV positive patient. Patients that  aren't HIV positive are not returned, and thus aren't present in the  result map.

For each HIV positive patient, the Result object contained in the  result map holds the actual first observation that gave an HIV positive  status to the patient.