What are rules?
In the context of logic service, 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]
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 answered with any of the following concepts:
- viralLoad = first HIV VIRAL LOAD
- viralLoadQual = first HIV VIRAL LOAD, QUALITATIVE
- cd4Count = first CD4 COUNT < 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
catch (LogicException e)
}
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]
Registering a Rule with Logic Service
Now that we have a functioning HIVPositiveRule, 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 and LogicServiceImpl implementations and track the changes. As soon as the mechanism is established, this section will be updated.
[edit]
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.