Title: Design question
Hello,

After some experimenting, I have come to a point where I am wondering if my solution is the correct one.

Let me summarize:

I have a Java application which is shared by many user (contexts) sharing the same Rete instance.

The rule engine is supposed to pilote several things in my app (a prescription system for a hospital):
- find the correct protocole or drug to prescribe
- initialize the doses, etc... Depending on drug, patient, etc...
- choose the correct widgets to display (a dose can be a combo box, a text field, etc... Depending on the property type and drug)
- choose the correct frames or windows to display
- give warnings
- many more things

The main problem when describing this kind of world with rules is that you can potentially have more than one rule firering for a given question, when actually, you can only handle one response (a dose can be either entered as a combo box or a text field, but not both at the same time:-)

To avoid this runnaway problem, I chose to use salience and add a property to my context which will say if I already have a valid answer for a given question.

Technically speaking, when a new user logs in, I create a context and definstance it. For each request-response loop coming from the user, I first initialize variables depending on the data entered on the screen, then set a property telling me I am ready to run, then I run the rete instance which will have the effect of firering some rules and have some properties of my context to be updated. I then set the ready to run property to false (to avoid being run by another use who would be concurently doing something) then generate the response and send it back to the user...

For example, here are a rule which will fire when the user selects a response to find various kinds of aspirines:

(defrule ASP1
    (OrmedContext
        (readyToRun TRUE)
        (needScript TRUE)
        (OBJECT ?c)
    )
    (CompleterResponseElement
        (ormedContext ?c)
        (key "ASP")
    )
    =>
    (bind ?s (new OrmedScript "ASP" "Apirines diverses"))
    (call ?s addInstruction (new OrmedScriptInstruction "ASP1" "Aspirine Cardio (100mg)" FALSE TRUE TRUE TRUE))
    (call ?s addInstruction (new OrmedScriptInstruction "ASP2" "Aspirine 500mg" FALSE TRUE TRUE TRUE))
    (call ?s addInstruction (new OrmedScriptInstruction "ASP3" "Aspirine 1000mg" FALSE TRUE TRUE TRUE))
    (call ?c setSelectedScript ?s)
)

I also have an additional rule which catches the age of the patient:

(defrule AGE_HIGH
    (declare (salience -100))
    (OrmedContext
        (readyToRun TRUE)
        (patient ?p)
        (OBJECT ?c & :(?c needsKey "ageCheck"))
    )
    (Patient
        (age ?a & :(> ?a 65))
        (OBJECT ?p)
    )
    (OrmedScript
        (ormedContext ?c)
        (key ~ "000")
        (OBJECT ?s)
    )
    =>
    (call ?s addInstruction (new OrmedScriptInstruction "000" "Beware, senior citizen!" FALSE FALSE TRUE TRUE))
    (call ?c foundKey "ageCheck")
)

As you can see, I need to have a “ageCheck” property to avoid firering this rule several times hence adding several time the same warning.
I also have a needScript property to say I need a new script to be set.
I also have a catch all rule, if the script or drug was not found:

(defrule CATCH_SCRIPT
    (declare (salience -100))
    (OrmedContext
        (readyToRun TRUE)
        (needScript TRUE)
        (OBJECT ?c)
    )
    =>
    (bind ?s (new OrmedScript "000" "No script found!"))
    (call ?c setSelectedScript ?s)
)

At the same time, I have many other rules for widgets, components, etc...
All these objects (OrmedScript, etc...) are definstanced which will allow me after to test what instruction was selected, etc...

By doing things like that, it is getting really difficult to follow (and debug) what is going on, since many rules can fire when actually I was just asking for one thing at a time.

So (sorry, it is getting long, but I think it is fundamental)... I thought of changing things and putting them “the other way round”.
Basically, my application (in Java) at various points in the execution will need to get values, objects, data, whatever...
Why not implement a function which says something like:

“give me the value for key XYZ”

Run the rule engine with that “mission” and once the key is found, go on to the next question, etc...
In practice, for my example, I will first need a script. I would set the variables in my context then set the key to “script”, definstance the context, run, undefinstance the context. Normally at that point I should have gotten the script. The rules would then look like:

(defrule SCRIPT_AGE_HIGH
    (declare (salience -100))
    (OrmedContext
        (key "script")
        (patient ?p & :(> (get ?p age) 65))
        (OBJECT ?c)
    )
    =>
    (call ?s addInstruction (new OrmedScriptInstruction "000" "Beware, senior citizen!" FALSE FALSE TRUE TRUE))
)

(defrule SCRIPT_ASP
    (OrmedContext
        (key "script")
        (completerResponseElement ?r & :(eq "ASP" (get ?r key)))
        (OBJECT ?c)
    )
    =>
    (bind ?s (new OrmedScript "ASP" "Apirines diverses"))
    (call ?s addInstruction (new OrmedScriptInstruction "ASP1" "Aspirine Cardio (100mg)" FALSE TRUE TRUE TRUE))
    (call ?s addInstruction (new OrmedScriptInstruction "ASP2" "Aspirine 500mg" FALSE TRUE TRUE TRUE))
    (call ?s addInstruction (new OrmedScriptInstruction "ASP3" "Aspirine 1000mg" FALSE TRUE TRUE TRUE))
    (call ?c setSelectedScript ?s)
)

I would also have a catch all rule if a script was not found. Once the script is found, I undefinstance the context (which should stop the firering).

My question is:

How efficient is it to definstance / undefinstance for each key I am looking for? Imagine a user session as:

  • Find the right script (one definstance / undefinstance), there could be up to 2000 scripts available (rules)
  • Find the right drug and initialise with correct properties given a selected instruction in the script
  • In the same request, once the drug is found and initialised, display a data entry screen which may have 10 fields. Each field will need a run of the rule engine to determine which widget to use considering the property, context, drug, etc...
  • Once the user has entered the data, update the script (if the script suggested 2 different pain killers, and one is chosen, the second one has to become disabled for example).

Now, this is only the tip of the iceberg. Of course, there are additional rules to maybe calculate interractions between drugs, calculate doses depending on lab results, etc...

All this summarizes to:

  • Simplicity in rule design and programming / many “small runs” to obtain results
  • Complexity in rule design and programming / one run per request which should “set all” in one go

Mmmm, I wonder if (a) anyone read up to here :-) and (b) if this makes sense. Indeed, If Jess is used as the centerpiece of an application, others must have falling on those kinds of problems?

Another last point: most of the rules will be generated from knowledge bases (drugs, protocoles, etc...), only catch rules, special situations, etc... Will be hand written.

Thanks for reading!

Alex

--
Alexander Lamb
Groupe Serveurs Applicatifs
Division d'Informatique Médicale
Hôpitaux Universitaires de Genève
21 rue Micheli-du-Crest
CH-1211 Genève 4 / Switzerland
Tel: +41-22 372.48.46 Fax: +41-22 382.86.80
[EMAIL PROTECTED] / http://www.hcuge.ch

Reply via email to