...
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:
...
Code Block |
---|
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]
Registering a Rule with Logic Service
...
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.
...