A new "rule" module will be authored which has the core set of functionality that is desired. This functionality will be primarily focused on producing a particular piece of data for a single patient or a cohort of patients. This module will use the "org.openmrs" namespace, rather than the conventional "org.openmrs.module" namespace in order to more easily support the goal of eventually incorporating this module into core. By keeping it as a module initially, this allows implementations running older versions of OpenMRS (1.6, 1.8, 1.9, etc) to take advantage of, and test out, this work, without requiring them to upgrade. It also will allow the module to evolve at a different pace than the core code.
TODO: What do we think of the name of this module? rule? patientcalculation? patientdata?
For the purposes of this design, and for brevity, I will use "Rule". If we change the name, we can replace "Rule" throughout as appropriate.
The existing "org.openmrs.logic" package that exists in core will be removed into the existing logic module. The logic module will be updated such that it requires the module described above, and that it implements the interfaces created and exposed in it. The logic module will no longer be a "core" or "required" module within an OpenMRS distribution. Backwards compatibility will be maintained with a few minor exceptions, including the need to explicitly require the module within any module that uses it, and the need to use Context.getService(LogicService.class) rather than Context.getLogicService().
Requirements for the new functionality
Design for new functionality
Rule / ParameterDefinition
A Rule represents a definition that can be evaluated to produce patient data. A Rule can expose parameters which control the results of it's calculation.
The RuleContext contains any contextual information that may be shared across one or more Rule evaluations. This includes the "index date" for the evaluation and a cache for storing the results for previously evaluated rules. The index date represents the date on which the evaluation should occur. It should essentially replace any call for "new Date()" in evaluation code, and should return the data that was accurate as of that particular date and time.
A Result is the data that is produced from evaluating a Rule for a single patient. Results are strongly typed, but provide a convenience method for casting to other datatypes.
We will likely employ a library of utility methods as well, to support conversion of Result types. For example:
A CohortResult is the data that is produced from evaluating a Rule for a Cohort of patients. It is essentially a wrapper of a Map<Integer, Result>, but provides the flexibility to add additional methods and/or data as needed down the road.
A RuleEvaluator is responsible for evaluating one or more types of Rules into Results. This is where the bulk of all calculations occur, either by performing these calculations directly within the evaluator, or by calling service methods / DAOs that perform calculations. RuleEvaluators will likely be wired to Rule classes either via a registry or via annotations.
As an implementation detail, we probably want an abstract class to simplify writing RuleEvaluators that evaluate patients one at a time, like:
A RuleProvider is responsible for retrieving a Rule instance given a rule name and an optional configuration string. A typical implementation would be such that ruleName is the rule class to instantiate and configuration represents the serialized property values that need to be configured on this rule instance, however it is totally up to the Provider to define this. For example, to retrieve the Rule for "Most Recent Weight":
Rule Name: org.openmrs.rule.definition.MostRecentObsRule
Configuration: concept=<UUID for Weight (KG) concept>
There would then be a RuleProvider registered to handle this type of Rule which would know it needed to first instantiate a new instance of MostRecentObsRule, configure it's properties via the parsed values from the configuration string, and then return configured Rule instance. Like RuleEvaluators, RuleProviders will likely be wired to Rule classes either via a registry or via annotations.
A TokenRegistration represents a saved Rule instance in the database, and includes a unique name, the RuleProvider, the ruleName, and the configuration for the rule. The intention is to allow a fully configured Rule instance to be retrieved given a unique name String. This class is a hibernate-managed class.
The RuleService is the primary mechanism for evaluating Rules and for associating Rule instances with saved tokens.