Wiki Spaces


Get Help from Others

Q&A: Ask OpenMRS
Discussion: OpenMRS Talk
Real-Time: IRC Chat | Slack


Page tree
Skip to end of metadata
Go to start of metadata


  • Existing logic engine can be implemented as an optional module without too much trouble.
  • A shared Rule interface
    • Rules provide a method to evaluate within a context + parameters
  • A shared Result interface
    • Result declares it's type explicitly
    • Provides a mechanism for consumers to easily distinguish between & use lists vs. single values
  • Provides an easy way to coerce results between different types and between lists vs. single value
  • A shared LogicContext interface within which rules are evaluated
  • Centralized token registration by providing token, rule ID, provider, and configuration (unique across providers)
  • Support for parameters.
  • Caching is deferred to rule evaluators for now

Skeleton Interfaces / Implementations


interface Rule {

  public Set<RuleParameterInfo> getParameterList();  // TODO: We didn't discuss this interface, but we did agree that rules need to support parameters



interface RuleEvaluator {

  boolean canEvaluate(Rule rule);  // TODO: This might be better implemented through annotations.  Needs further discussion

  Map<Integer, Result> evaluate(Cohort, Rule, Map<String, Object>, RuleContext); // TODO: We probably want to wrap Map<Integer, Result> in a proper class



interface RuleProvider {

  Rule getRuleInstance(String ruleName, String extraConfig); // ruleName could be anything the provider wants.  might typically be a classname.



interface / implementation RuleService/RuleServiceImpl {

  RuleContext createContext(); // Ensures that RuleContext can be overridden as needed

  void registerToken(String token, RuleProvider provider, String ruleName, String extraConfig);

  void unregisterToken(String token, RuleProvider provider); // provider for safety

  (the below methods might also have implementations without RuleContext and/or without params for convenience)

  Result evaluate(Integer ptId, String token, Map<String, Object> params, RuleContext context) {

    // create a cohort with a single patient, call the evaluate method on the cohort, return the result for that patient


  Map<Integer, Result> evaluate(Cohort c, String token, Map<String, Object> params, RuleContext context) {

    // find the appropriate RuleProvider, ruleName, extraConfig for the given token; get the Rule from the RuleProvider passing in the ruleName and extraConfig; call evaluate method on the Rule


  Result evaluate(Integer ptId, Rule rule, Map<String, Object> params, RuleContext context) {

    // construct a cohort of one; get the evaluator for the passed rule; call evaluate passing in the cohort, params and the context; return the result for the patient


  Map<Integer, Result> evaluate(Cohort c, Rule rule, Map<String, Object> params, RuleContext context) {

    // get the evaluator for the passed rule; call evaluate passing in the cohort, params and the context; return the result




interface RuleContext {

  // TODO: Figure out whether this has indexDate, any caching, etc.



interface Result {

  public Object getValue();

  public Date getDatetime(); // Needs more discussion if this is appropriate on the base interface (eg. what to do for Lists)

  public String formatAsString();


class NumericResult {

  private Double result;

  private Date datetime;

  public Object getValue() { return result; }

  public Date getDatetime() { return datetime; }


class ListResult {

  private List<Result> results;

  public Object getValue() { return results; }

  public Date getDatetime() { // Not sure what to do here.  Get the datetime of the first result?  Maybe this method doesn't belong


class ObsResult {

  private Obs obs;

  public getValue() { return obs; }

  public Date getDatetime() { return obs.getObsDatetime(); }


Use of Result

For coercing / converting Results to scalars for use in comparisons etc, for example:

if (eval("BMI") > 23) { ... }, we discussed a few possible approaches:  (TODO:  Decide on one)

  1. Add methods like "asDouble()", "asDate()", "asString()", "asBoolean()" to the Result interface (as we have now)
  2. Add single method to Result like:  eval("BMI").coerce(Double.class) > 23
  3. Add utility method like:  LogicUtil.toDouble(eval("BMI")) > 23
  4. Add utility method like:  LogicUtil.coerce(eval("BMI"), Double.class) > 23
  5. Add a variety of converters like:  new DoubleConverter().convert(eval("BMI")) > 23

Benefit of #5 is that it is cleaner than a massive utility method with lots of conditional logic, and that it allows modules to plug new converters into the framework as needed.  It might look something like this:

interface ResultConverter<T> {

  public T convert(Result);


class DoubleConverter<Double> {

  public Double convert(Result result) { return Double.valueOf(result.toString()); }


We ran out of time before we could really discuss next steps on all of this.  Should we carve out time in another design forum in December?


View original notes at


  1. We still need to address a number of issues (i.e., agree on requirements for the new service), including:

    • Finalizing return types
    • Win mentioned he would prefer pre-defined objects instead of just Map for parameters and multiple results
    • Finalize decision on how/if we provide any TTL hints for rules
    • Determine how consumers can get to the specific rule provider (e.g., CHICA team needs to be able to get to "old logic" datasources)
    • Do we include dependency information in the Rule interface to encourage rules to declare their dependencies (helps in rule administration & caching solutions)?
    • Can/should rules declare their parameters (i.e., do we want a shared way of inspecting rule parameter info – e.g., RuleMetadata)?
  2. Unknown User (nyoman)

    Declaring parameter is a must I think Burke. So that's definitely a good topic to cover.

    class EvaluationParameterMetadata {
        String parameterName;
        Class parameterType;
        Boolean mandatory;
        Object defaultValue

    We could also need some sort of validate method to validate a dependency is exists or not.

    For example rule A call rule B in the evaluation process. Rule A must be able to say that rule A need rule B to be exists and the logic engine should validate this.

    1. Unknown User (darius)

      Need some tweak on this to allow things like List<EncounterType> (because "Class parameterType" isn't enough to capture that)