Rule Tutorial

From DOC

Jump to: navigation, search

Contents

Overview

This tutorial begins with a set of clinical scenarios that employ rules. It then builds a plugin that implements these rules to fulfill the clinical scenarios in several stages.

Prerequisites

This is a technical tutorial. You will customize an existing Tolven installation, most likely one that you have installed on your development system, with a plugin you will create as part of this tutorial. You will need a Tolven development environment, typically in Eclipse.

Your tolven-config/plugins.xml should already include org.tolven.prototype.applications and org.tolven.prototype.application.trims.

This tutorial may modify certain trims that you have already uploaded.

Setup a new plugin:

  1. If you have not done so already, you will need to create a plugin project in Eclipse

Clinical Scenarios

Implementing each of the following rules depends access to and use of the Tolven prototype application.

  1. If a glucose observation is entered with a value greater than 140, place "at risk of diabetes mellitus" (SCT 161641009) on the problem list for this patient
  2. If there is more than one Glasgow Coma scale entry for a patient within 30 days and if a Glasgow coma scale score is lower than the one preceding it, post an alert on the activity page saying that "This patient's level of consciousness is declining".
  3. If a diastolic blood pressure greater than 140 is entered, place the patient on the "Hypertension" registry.
  4. If a body temperature (SCT 105723007) greater than 38.5 C is entered, place "fever" (finding - SCT 386661006) on the active problem list
  5. If a body temperature less than 38 C is then entered, move the "fever" entry from the active problem list to the inactive problem list
  6. If a patient has a warfarin oral tablet entry in their medication list, and if the patient has a needle biopsy of liver (SCT 303588003) documented, post an alert on the activity list that says "this patient is at high risk for bleeding"
  7. If a patient has two successive body weight measurements in 30 days or less where the most recent measure is 10 pounds or more less than the earlier measure, place "weight loss finding" (SCT 89362005) on the problem list.

Organizing Rules

There is no strict strategy for organizing rules and rule packages, but for our purposes, we will organize the rules around the ultimate goal of the clinical scenarios.

  • Detecting Diabetes Mellitus
  • Populating the Hypertension Registry
  • Creating a Fever Entry on the Problem List
  • Alerting for Bleeding Risk
  • Detecting Unusual Weight Loss

We will name our rule package files accordingly and place all rules relating to each area in a separate rule file. You should know that rules execute in a single "working memory". That is, all rules are active all the time, and in general, there is little reason to try to externally control which rules execute and which do not. The rule itself should contain all of the conditional logic to determine when and if it should fire.

How rules work in general

If you understand how rules work, you can skip this section.

The only attempt we will make to control the execution of rules is to ensure that we've got all the data we need to make our decisions. This is typically done in the individual rules. That is, rules should contain all the conditions needed to control their execution. If the rule should not fire in a particular situation, then the rule should test for that situation. Consider the rule:

if we had some ham, then we could have some ham and eggs.

But we don't want it to fire if there are no eggs. If we do not test for eggs, then the conclusion of this rule would be incorrect.

You might think of the egg question as external to the rule "I can't allow that rule to fire if there are no eggs" So the advice is to write the rule correctly:

if we had some ham and if we had some eggs, then we could have some ham and eggs.

And if there's more criteria, then the rule should be even more explicit:

if we had some ham and if we had some eggs and if we had a frying pan, we could have some ham and eggs.

Now, this sets up for another capability of rules, called chaining. Consider the assertion made in this first rule in a different rule that says:

if we could have some ham and eggs and we want to have ham and eggs, then make ham and eggs

The conclusion of the first rule is used in the second rule. Without the conclusion of the first rule, the second rule would not fire. Regardless of the desire for ham and eggs, ham and eggs would not be prepared unless

  1. both ingredients existed, and
  2. there was a desire for ham and eggs.

To be complete, some external caller would assert into working memory the fact that we have eggs if indeed that were the case. It could also assert that we indeed want to have ham and eggs. Then, if somehow, ham became available, the rule would fire and thus make ham and eggs available.

Interestingly, this type of rule syntax makes no provision for else/otherwise. Instead, such a case is written as another rule. Pardon my grammar in the following:

if not we have eggs, then buy eggs and assert we have eggs.

Change Placeholder Structure

The rule that we will be writing depends on certain fields being present in the observation placeholder. Update your "observation" placeholder so that it contains the "code" field if it is not already there. One way to do this without having to edit the echr-patient.application.xml file is to create a new application.xml file (for example, tutorial.application.xml) in your plugin:

<application xmlns="urn:tolven-org:menuStructure:1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
	xsi:schemaLocation="urn:tolven-org:menuStructure:1.0 http://tolven.org/xsd/menuStructure.xsd">
  <extends path="echr:patient:observation">
      <field name="code" internal="code01">
          <from>#{act.code.value}</from>
      </field>
  </extends>
</application>

Change a Trim

Assuming that you have a local version of the trims provided by Tolven, we need to verify that the Trim we will use have the correct format.

Find the glocose.trim.xml file and make sure that the code attribute in the act is as follows:

<code>
    <CD>
        <code>33747003</code>
        <codeSystem>2.16.840.1.113883.6.96</codeSystem>
        <codeSystemName>SCT</codeSystemName>
    </CD>
</code>

Create Rule Package

Create file under app/rules named diabetesmellitus.drl The package name will be com.myorg.diabetesmellitus.

The file should look something like this:

package com.myorg.diabetesmellitus
// See http://wiki.tolven.org/doc/index.php/Plugin:org.tolven.component.tolvenejb#PlaceholderFacts
// for details on the following annotation:
//@use echr placeholders

import org.tolven.conceptgroup.*;
import com.myorg.my.MyEventLocal;

global org.tolven.app.AppEvalAdaptor app;

rule "Create Glucose Group"
    when
        eval(true)	// One-time rule
    then
        ConceptGroup cg = new org.tolven.conceptgroup.ObjectFactory().createConceptGroup();
        cg.setName( "glucose");
        // Repeat for each code that should match for diabetes
        cg.addCode( "2.16.840.1.113883.6.96", "33747003" );
        insert( cg );
end

rule "glucose reading"
    when
        observation( $code: code,  $value: value > 140, $patient: patient )
        ConceptGroup( name=="glucose", codes contains $code)
    then
        app.info( "Patient " + $patient.getField("lastName") + " at risk for diabetes with glucose of " + $value );
end

Show Rules Debug Messages

If you use the optional LOG4J configuration, each rule firing is displayed along with the facts used by that rule.

If you don't want to use this optional configuration and know how to configure log4j, here are the details:

Edit the log4j setup file to enable the output of additional trace messages. In JBoss, edit the file tolven-jboss-4.2.2.GA/server/tolven/conf/jboss-log4j.xml and add:

   <!-- Debug for Tolven Rules  -->
   <category name="org.tolven.rules">
      <priority value="DEBUG"/>
   </category>

You will then have to raise the threshold to the debug level which is likely to cause a lot of unwanted debug output.

Test Rules

To test these new rules

  1. Build and deploy your plugin
  2. Run configPhase3 (no code changes this time)
  3. Login to the application, select a patient, and create a Blood Glucose observation with a value over 140.
  4. Observe the server log to see that the message displays

Add a SessionBean

Create a session bean named MyEventBean.

package com.myorg.my.bean;

import javax.ejb.Local;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import org.apache.log4j.Logger;
import org.tolven.app.entity.MenuData;

import com.myorg.my.MyEventLocal;
//import com.myorg.my.entity.MyEntity;


@Stateless()
@Local(MyEventLocal.class)
public class MyEventBean implements MyEventLocal {
	private Logger logger = Logger.getLogger(this.getClass());
	
	@PersistenceContext private EntityManager em;

	public void processObservation( MenuData mdObservation) {
		logger.info("We're in a session bean with " + mdObservation);
//		MyEntity event = new MyEntity();
//		event.setTitle(mdObservation.getPath() + " code: " + mdObservation.getField("code") + " value: " + mdObservation.getField("value"));
//		em.persist(event);
	}
}

Create local proxy to that session bean named MyEventLocal

package com.myorg.my;

import org.tolven.app.entity.MenuData;

public interface MyEventLocal {
	/**
	 * A simple interface to a sessionBean method
	 * @param mdObservation The observation that we are processing right now
	 */
	public void processObservation( MenuData mdObservation);

}

Add two more rules to the rules package:

rule "Initialize our custom event bean"
	when
		not MyEventLocal()
	then
		insert(app.lookupResource( "tolven/MyEventBean/local"));
end
rule "glucose reading 2"
	when
		observation( $obs:menuData, $code: code,  $value: value > 140, $patient: patient )
		ConceptGroup( name=="glucose", codes contains $code)
		$myEvent: MyEventLocal()
	then
		$myEvent.processObservation($obs);
end

Test Rules with session bean

To test the updated rules

  1. Build and deploy your plugin
  2. Run configPhase1 (Server code changes have been made this time)
  3. Run configPhase3 (To reload and compile the rules)
  4. Set a breakpoint in the new session bean
  5. Login to the application again, select a patient, and create another Blood Glucose observation with a value over 140.
  6. Observe the the breakpoint

Add an entity bean to the project

Create a new Java class named com.myorg.my.entity.MyEntity

package com.myorg.my.entity;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

/**
 */
@Entity
public class MyEntity implements Serializable {
	
	private static final long serialVersionUID = 1L;

	@Id
    @GeneratedValue(strategy=GenerationType.TABLE, generator="CORE_SEQ_GEN")
    private long id;

    @Column
    private String title;

    /**
     * Construct an empty Account or Sub-account. A sub-account should have a non-null Parent Account. Account trees cannot have circular references. 
     * @see #getParent
     */
    public MyEntity() {
    }
    
    /**
     * The unique internal ID of this entity. This ID has no meaning other than uniqueness. 
     * Leave Id null for a new record. The EntityManager will assign a unique Id when it is persisted.
     */
    public long getId() {
        return id;
    }

    public void setId(long val) {
        this.id = val;
    }

    public boolean equals(Object obj) {
        if (!(obj instanceof MyEntity)) return false;
        if (this.getId()==((MyEntity)obj).getId()) return true;
        return false;
    }

    public String toString() {
        return "My Entity: " + getId();
    }

    public int hashCode() {
    	if (getId()==0) throw new IllegalStateException( "id not yet established in MyEntity object");
        return new Long( getId()).hashCode();
    }

    /**
     * A String containing the title for this MyEntity. 
     */
    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }
}

Uncomment the code in MyEventBean that uses this EntityBean.

Test Rules with Entity bean added

To test these new rules

  1. Build and deploy your plugin
  2. Run configPhase1 (Code has been updated)
  3. No need to run configPhase3 - no rule or metadata changes
  4. Login to the application, select a patient, and create a Blood Glucose observation with a value over 140.
  5. Observer the entity written to the database table myentity (in PostgreSQL, it may be in public schema)
Personal tools