...
What
...
are
...
rules?
...
In
...
the
...
context
...
of
...
...
...
,
...
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.
...
[
...
...
]
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;
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
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.