Re: JESS: Re: calculating benefit costs
In the interest of exploration and education, here's a few random thoughts. The approach wolfgang mentions is an old knowledge base technique. I like to use it for data that is constant-like. Things like code reference tables. One example of this is auto insurance rating. Many companies rate based on zip code and assign a numeric value for each zip code. If a developer hard codes the constant in the rule, it means every time the value changes, the rule would need to be updated. The problem is actually worse than that. Say a company sells insurance in 5 states and there's a total of 500 zip codes. Using the hard coded approach, the developer would need to write 500 rules. If on the other hand, the developer uses facts to match the insurance policy to a zip code rating fact, we can easily change the rating value for a zipcode and not affect the rules. The downside is we need to have good management tools for editing the reference data. All of the reference data should be versioned and basic validation should be performed on the data before it is deployed to production. Unfortunately, all of the products on the market today do not provide robust tools for managing knowledge base data. In the past, I've written custom applications for managing knowledge data in financial applications. For applications that have lots of code reference data that changes regularly, I strongly recommend using fact data instead of hard coding constants. The other benefit is tends to reduce lots of procedural code in the RHS of the rule. Instead of using lots of if/then/else, the rule tends to match the facts in the LHS and use the reference data in the RHS. To put it another way, some of the calculations have become pre-calculated codes. This can make the rules easier to read and maintain. Also, by precalculating some of the data, we reduce the amount of work the rule engine has to perform each time. Using these types of knowledge base techniques also makes it easier to write proof and validation routines to make sure the pre-calculated tables are accurate. I would recommend reading books on knowledge base and expert systems to get a broader understanding of rule programming. Most of the business rule books out there are really just user manuals and don't go into details on the design patterns used in expert systems. Gary Riley's book is a great place to start. As wolfgang stated, On Sat, Jan 8, 2011 at 10:39 AM, Wolfgang Laun wolfgang.l...@gmail.com wrote: All the pundits advocate to put the decision logic into the LHS. Opinions vary a little wrt. to the use of static facts to reduce the number of rules. Some say, The more rules the merrier. I feel that using static facts for lookup of data is justified, considering this: Fact data is easier to change than rule code. Putting the data into facts is one way of avoiding hard coded constants. Less code means less room for bugs. Now to the one rule that does it all, posted by Derek, with the cascading if on the RHS. Looking at the code, I've seen a few things which might be worth noting. calcAgeAsOf is called many times. Why not call once and save the result? Age limits and costs should be kept in a list to avoi code repetition by using a loop. globals are one way of making such data available. Setting a variable depending on ?rel and a single logic avoids code duplication. The cost result is stored in the object using a call of some Java method. But bypassing Jess when modifying facts is not a good idea, most of the time. Below is the much reduced code. Of course, it still has the major defeciency of too much logic on the RHS, (defglobal ?*limAge* = (list 30 40 50 60 70 ) ?*cstEmp* = (list 11.55 18.95 38.35 65.95 104.35) ?*cstRel* = (list 4.20 6.05 10.90 17.80 27.40) ) (defrule setCalculatedCostGCI20k5k ?hbj - (HrBenefitJoin (hrBenefitConfigId 1-76) (benefitJoinId ?bjid) ;; (calculatedCost ?cost) (calculatedCost 0) ;; (OBJECT ?obj) (coveredPersonId ?cPer) (payingPersonId ?pPer) (relationshipId ?rel)) (Person (personId ?cPer) (dob ?dob) ;; (OBJECT ?objP) ) = ; call once, bind result (bind ?years (call ?objP calcAgeAsOf(call com.arahant.utils.DateUtils now ))) ; avoids cut-and-paste repettion of code (bind ?costs (if (eq ?rel nil) then ?*cstEmp* else ?*cstRel* fi)) (bind ?cost nil) (for (bind ?i 1) (= ?i (length$ ?*limAge*)) (++ ?i) (if ( ?years (nth$ ?i ?*limAge*)) then (bind ?cost (nth$ ?i ?costs)) (break) ) ) (if (eq ?cost nil) then (printout t age greater than 69... ?years for ?bjid crlf) else ; Bypassing Jess when modifying is not a good idea, most of the time. ; (call ?obj overrideAgeCost ?cost) (modify ?hbj (calculatedCost ?cost)) ) ) -W On 7 January 2011 19:44, Jason Morris
Re: JESS: Re: calculating benefit costs
I found this book (Giarratanno Riley) to be of very low value for my purposes. I have yet to find a decent book about rule base programming. Jess In Action is the only one that's been useful for me. A couple of Drools books I've read were ridiculous. Most AI and knowledge base books are 95% fluff. On Jan 9, 2011, at 9:33 AM, Peter Lin wrote: I would recommend reading books on knowledge base and expert systems to get a broader understanding of rule programming. Most of the business rule books out there are really just user manuals and don't go into details on the design patterns used in expert systems. Gary Riley's book is a great place to start. To unsubscribe, send the words 'unsubscribe jess-users y...@address.com' in the BODY of a message to majord...@sandia.gov, NOT to the list (use your own address!) List problems? Notify owner-jess-us...@sandia.gov.
Re: JESS: Re: calculating benefit costs
All the pundits advocate to put the decision logic into the LHS. Opinions vary a little wrt. to the use of static facts to reduce the number of rules. Some say, The more rules the merrier. I feel that using static facts for lookup of data is justified, considering this: - Fact data is easier to change than rule code. - Putting the data into facts is one way of avoiding hard coded constants. - Less code means less room for bugs. Now to the one rule that does it all, posted by Derek, with the cascading if on the RHS. Looking at the code, I've seen a few things which might be worth noting. - calcAgeAsOf is called many times. Why not call once and save the result? - Age limits and costs should be kept in a list to avoi code repetition by using a loop. globals are one way of making such data available. - Setting a variable depending on ?rel and a single logic avoids code duplication. - The cost result is stored in the object using a call of some Java method. But bypassing Jess when modifying facts is not a good idea, most of the time. Below is the much reduced code. Of course, it still has the major defeciency of too much logic on the RHS, (defglobal ?*limAge* = (list 30405060 70 ) ?*cstEmp* = (list 11.55 18.95 38.35 65.95 104.35) ?*cstRel* = (list 4.20 6.05 10.90 17.80 27.40) ) (defrule setCalculatedCostGCI20k5k ?hbj - (HrBenefitJoin (hrBenefitConfigId 1-76) (benefitJoinId ?bjid) ;; (calculatedCost ?cost) (calculatedCost 0) ;; (OBJECT ?obj) (coveredPersonId ?cPer) (payingPersonId ?pPer) (relationshipId ?rel)) (Person (personId ?cPer) (dob ?dob) ;; (OBJECT ?objP) ) = ; call once, bind result (bind ?years (call ?objP calcAgeAsOf(call com.arahant.utils.DateUtils now ))) ; avoids cut-and-paste repettion of code (bind ?costs (if (eq ?rel nil) then ?*cstEmp* else ?*cstRel* fi)) (bind ?cost nil) (for (bind ?i 1) (= ?i (length$ ?*limAge*)) (++ ?i) (if ( ?years (nth$ ?i ?*limAge*)) then (bind ?cost (nth$ ?i ?costs)) (break) ) ) (if (eq ?cost nil) then (printout t age greater than 69... ?years for ?bjid crlf) else ; Bypassing Jess when modifying is not a good idea, most of the time. ; (call ?obj overrideAgeCost ?cost) (modify ?hbj (calculatedCost ?cost)) ) ) -W On 7 January 2011 19:44, Jason Morris jason.c.mor...@gmail.com wrote: Hi Derek, OK OK... I wasn't going to say anything either but with Peter and James added to Wolfgang, I have to pile too :-) IMHO there are a number of maxims of rule-based programming that you're breaking here. Ernest has put the first one most succinctly in the past: Use many smaller rules that do one thing well rather than one Über Rule that boils the ocean. Declarative programming should say what action(s) are to be performed when certain facts are present, but not attempt to implement those actions directly on the RHS. The second, as James and Peter pointed out, is not to do Java programming on the RHS. If you make the RHS a series of method calls that take the variable bindings from the LHS as arguments, your rules will be much cleaner and maintainable. Finally -- and this is just a plain programming nit -- I noticed the apparent use of magic numbers in your conditions. What about all these calls to overrideAgeCost()? Where are those args coming from? Are they part of some policy? What if that policy changes? Then your rules would be in a dirty form. Wouldn't it be better to get those values from some cache or database with most current values? Something that might help you: I've been collecting rule-based *metaphors* lately -- different ways of thinking about using rules. One metaphor that has been particularly productive has been thinking about digestion and the passing of data through a series of modules like a digestive tract. Obviously, if you carry the metaphor too far ... well you see where the garbage in/ garbage out saying comes into play. :-) But the idea is that you are moving the data through different states, partially processing it each time a new rule module has a crack at it. This has precedent in UNIX/LINUX with pipes -- same idea. Old wine in a new bottle? Perhaps. But this way, you can clearly separate out concerns, add pre and post processing functions, and test partial results by simply disabling/enabling certain modules in the sequence. Cheers, Jason -- Jason Morris Chairman, Rules Fest 2010/2011 http://www.rulesfest.org Morris Technical Solutions LLC consult...@morris-technical-solutions.com (517) 304-5883
Re: JESS: Re: calculating benefit costs
Thanks for the help guys. Here is what I ended up with. This works, but I'm sure it's not the most efficient way to solve the problem. (defrule setCalculatedCostGCI20k5k (HrBenefitJoin (hrBenefitConfigId 1-76)(benefitJoinId ?bjid)(calculatedCost ?cost)(OBJECT ?obj)(coveredPersonId ?cPer)(payingPersonId ?pPer)(relationshipId ?rel)) (Person (personId ?cPer)(dob ?dob)(OBJECT ?objP)) = (printout t age = (call ?objP calcAgeAsOf(call com.arahant.utils.DateUtils now )) for ?bjid crlf) (if (eq ?rel nil) then (if ( (call ?objP calcAgeAsOf(call com.arahant.utils.DateUtils now )) 30) then (call ?obj overrideAgeCost 11.55) else (if ( (call ?objP calcAgeAsOf(call com.arahant.utils.DateUtils now )) 40) then (call ?obj overrideAgeCost 18.95) else (if ( (call ?objP calcAgeAsOf(call com.arahant.utils.DateUtils now )) 50) then (call ?obj overrideAgeCost 38.35) else (if ( (call ?objP calcAgeAsOf(call com.arahant.utils.DateUtils now )) 60) then (call ?obj overrideAgeCost 65.95) else (if ( (call ?objP calcAgeAsOf(call com.arahant.utils.DateUtils now )) 70) then (call ?obj overrideAgeCost 104.35) else (printout t age greater than 69 ... (call ?objP calcAgeAsOf(call com.arahant.utils.DateUtils now )) for ?bjid crlf)) else (if (neq ?rel nil) then (if ( (call ?objP calcAgeAsOf(call com.arahant.utils.DateUtils now )) 30) then (call ?obj overrideAgeCost 4.20) else (if ( (call ?objP calcAgeAsOf(call com.arahant.utils.DateUtils now )) 40) then (call ?obj overrideAgeCost 6.05) else (if ( (call ?objP calcAgeAsOf(call com.arahant.utils.DateUtils now )) 50) then (call ?obj overrideAgeCost 10.90) else (if ( (call ?objP calcAgeAsOf(call com.arahant.utils.DateUtils now )) 60) then (call ?obj overrideAgeCost 17.80) else (if ( (call ?objP calcAgeAsOf(call com.arahant.utils.DateUtils now )) 70) then (call ?obj overrideAgeCost 27.40) else (printout t age greater than 69 ... (call ?objP calcAgeAsOf(call com.arahant.utils.DateUtils now )) for ?bjid crlf (printout t setCalculatedCostGCI function fired for ?bjid . overrideAgeCost = (call ?obj overrideAgeCost) crlf) ;(printout t setCalculatedCostGCI function fired for ?bjid crlf) ) On Thu, Jan 6, 2011 at 10:32 AM, Wolfgang Laun wolfgang.l...@gmail.comwrote: This additional information does not require a fundamentally different approach. Using the same set of facts CostEmployee/CostSpouse, we can now use two rules, one calculating the cost for the employee, and the other one for the spouse. (defrule calc-self ?hbj - (HrBenefitJoin (hrBenefitConfigId 1-73) (calculatedCost 0) {relationshipId == nil} (payingPersonId ?pPer) (coveredPersonId ?cPer) ) (Person(personId?cPer) (dob ?dob) ) (CostEmployee (lo ?lo:(= ?lo (yrs ?dob))) (hi ?hi:(= ?hi (yrs ?dob)))(cost ?cost)) = (printout t Cost for ?cPer paid by himself: ?cost crlf ) ) (defrule calc-other ?hbj - (HrBenefitJoin (hrBenefitConfigId 1-73) (calculatedCost 0) {relationshipId != nil} (payingPersonId ?pPer) (coveredPersonId ?cPer) ) (Person(personId?cPer) (dob ?dob) ) (CostSpouse(lo ?lo:(= ?lo (yrs ?dob))) (hi ?hi:(= ?hi (yrs ?dob)))(cost ?cost)) = (printout t Cost for ?cPer paid by ?pPer : ?cost crlf ) ) The date calculation is abstracted into a (deffunction yrs (?dob) ... (return ?yrs) ) Implementing this in Jess or in Java is a simple programming exercise, but it presumably it depends on some technical/legal issues, e.g., it may not be possible to use today as a basis for calculating the difference in years from the person's dob. Since there's no information what to do with the resulting cost, it's just printed to standard output, but updating slot calculatedCost in HrBenefitJoin is straightforward. Cheers Wolfgang On 05/01/2011, Derek Adams dad...@arahant.com wrote: the facts that I am working with are: (HrBenefitJoin (hrBenefitConfigId 1-73)(benefitJoinId ?bjid)(calculatedCost ?cost)(OBJECT ?obj)(coveredPersonId ?cPer)(payingPersonId ?pPer)(relationshipId ?rel)) (Person (personId ?cPer)(dob ?dob)(OBJECT ?objP)) ?rel is what identifies the covered person as spouse or
Re: JESS: Re: calculating benefit costs
Although most folks won't complain, my only comment here is that the logic is all contained in the action side (RHS) of the rule in the form of if-then-else statements (basic Java) followed by more if-then-else (more basic Java) statements. This is basically procedural code put into a rulebase in a procedural manner. This begs the question, Is a rulebase actually needed for this problem? Not the way is has been solved here. I kind of like Wolfgang's original solution on 5 Jan which would be more of a rulebase approach and would make maintenance at a later date much easier. Yes, it has more rules (which is the object of a rulebase), BUT the logic is properly defined on the LHS where it belongs and each rule is independently incremental as it should be. Also, using the rulebase approach would allow expansion of the basic problem into more complex problems later on. SDG jco On Jan 7, 2011, at 8:15 AM, Derek Adams wrote: Thanks for the help guys. Here is what I ended up with. This works, but I'm sure it's not the most efficient way to solve the problem. (defrule setCalculatedCostGCI20k5k (HrBenefitJoin (hrBenefitConfigId 1-76)(benefitJoinId ?bjid)(calculatedCost ?cost)(OBJECT ?obj)(coveredPersonId ?cPer)(payingPersonId ?pPer)(relationshipId ?rel)) (Person (personId ?cPer)(dob ?dob)(OBJECT ?objP)) = (printout t age = (call ?objP calcAgeAsOf(call com.arahant.utils.DateUtils now )) for ?bjid crlf) (if (eq ?rel nil) then (if ( (call ?objP calcAgeAsOf(call com.arahant.utils.DateUtils now )) 30) then (call ?obj overrideAgeCost 11.55) else (if ( (call ?objP calcAgeAsOf(call com.arahant.utils.DateUtils now )) 40) then (call ?obj overrideAgeCost 18.95) else (if ( (call ?objP calcAgeAsOf(call com.arahant.utils.DateUtils now )) 50) then (call ?obj overrideAgeCost 38.35) else (if ( (call ?objP calcAgeAsOf(call com.arahant.utils.DateUtils now )) 60) then (call ?obj overrideAgeCost 65.95) else (if ( (call ?objP calcAgeAsOf(call com.arahant.utils.DateUtils now )) 70) then (call ?obj overrideAgeCost 104.35) else (printout t age greater than 69 ... (call ?objP calcAgeAsOf(call com.arahant.utils.DateUtils now )) for ?bjid crlf)) else (if (neq ?rel nil) then (if ( (call ?objP calcAgeAsOf(call com.arahant.utils.DateUtils now )) 30) then (call ?obj overrideAgeCost 4.20) else (if ( (call ?objP calcAgeAsOf(call com.arahant.utils.DateUtils now )) 40) then (call ?obj overrideAgeCost 6.05) else (if ( (call ?objP calcAgeAsOf(call com.arahant.utils.DateUtils now )) 50) then (call ?obj overrideAgeCost 10.90) else (if ( (call ?objP calcAgeAsOf(call com.arahant.utils.DateUtils now )) 60) then (call ?obj overrideAgeCost 17.80) else (if ( (call ?objP calcAgeAsOf(call com.arahant.utils.DateUtils now )) 70) then (call ?obj overrideAgeCost 27.40) else (printout t age greater than 69 ... (call ?objP calcAgeAsOf(call com.arahant.utils.DateUtils now )) for ?bjid crlf (printout t setCalculatedCostGCI function fired for ?bjid . overrideAgeCost = (call ?obj overrideAgeCost) crlf) ;(printout t setCalculatedCostGCI function fired for ?bjid crlf) ) On Thu, Jan 6, 2011 at 10:32 AM, Wolfgang Laun wolfgang.l...@gmail.com wrote: This additional information does not require a fundamentally different approach. Using the same set of facts CostEmployee/CostSpouse, we can now use two rules, one calculating the cost for the employee, and the other one for the spouse. (defrule calc-self ?hbj - (HrBenefitJoin (hrBenefitConfigId 1-73) (calculatedCost 0) {relationshipId == nil} (payingPersonId ?pPer) (coveredPersonId ?cPer) ) (Person(personId?cPer) (dob ?dob) ) (CostEmployee (lo ?lo:(= ?lo (yrs ?dob))) (hi ?hi:(= ?hi (yrs ?dob)))(cost ?cost)) = (printout t Cost for ?cPer paid by himself: ?cost crlf ) ) (defrule calc-other ?hbj - (HrBenefitJoin (hrBenefitConfigId 1-73) (calculatedCost 0) {relationshipId != nil} (payingPersonId ?pPer) (coveredPersonId ?cPer) ) (Person(personId?cPer) (dob ?dob) ) (CostSpouse(lo ?lo:(= ?lo (yrs ?dob)))
Re: JESS: Re: calculating benefit costs
So many if else statements on the RHS is unruly. On Jan 7, 2011, at 9:15 AM, Derek Adams wrote: Thanks for the help guys. Here is what I ended up with. This works, but I'm sure it's not the most efficient way to solve the problem. (defrule setCalculatedCostGCI20k5k (HrBenefitJoin (hrBenefitConfigId 1-76)(benefitJoinId ?bjid)(calculatedCost ?cost)(OBJECT ?obj)(coveredPersonId ?cPer)(payingPersonId ?pPer)(relationshipId ?rel)) (Person (personId ?cPer)(dob ?dob)(OBJECT ?objP)) = (printout t age = (call ?objP calcAgeAsOf(call com.arahant.utils.DateUtils now )) for ?bjid crlf) (if (eq ?rel nil) then (if ( (call ?objP calcAgeAsOf(call com.arahant.utils.DateUtils now )) 30) then (call ?obj overrideAgeCost 11.55) else (if ( (call ?objP calcAgeAsOf(call com.arahant.utils.DateUtils now )) 40) then (call ?obj overrideAgeCost 18.95) else (if ( (call ?objP calcAgeAsOf(call com.arahant.utils.DateUtils now )) 50) then (call ?obj overrideAgeCost 38.35) else (if ( (call ?objP calcAgeAsOf(call com.arahant.utils.DateUtils now )) 60) then (call ?obj overrideAgeCost 65.95) else (if ( (call ?objP calcAgeAsOf(call com.arahant.utils.DateUtils now )) 70) then (call ?obj overrideAgeCost 104.35) else (printout t age greater than 69 ... (call ?objP calcAgeAsOf(call com.arahant.utils.DateUtils now )) for ?bjid crlf)) else (if (neq ?rel nil) then (if ( (call ?objP calcAgeAsOf(call com.arahant.utils.DateUtils now )) 30) then (call ?obj overrideAgeCost 4.20) else (if ( (call ?objP calcAgeAsOf(call com.arahant.utils.DateUtils now )) 40) then (call ?obj overrideAgeCost 6.05) else (if ( (call ?objP calcAgeAsOf(call com.arahant.utils.DateUtils now )) 50) then (call ?obj overrideAgeCost 10.90) else (if ( (call ?objP calcAgeAsOf(call com.arahant.utils.DateUtils now )) 60) then (call ?obj overrideAgeCost 17.80) else (if ( (call ?objP calcAgeAsOf(call com.arahant.utils.DateUtils now )) 70) then (call ?obj overrideAgeCost 27.40) else (printout t age greater than 69 ... (call ?objP calcAgeAsOf(call com.arahant.utils.DateUtils now )) for ?bjid crlf (printout t setCalculatedCostGCI function fired for ?bjid . overrideAgeCost = (call ?obj overrideAgeCost) crlf) ;(printout t setCalculatedCostGCI function fired for ?bjid crlf) ) On Thu, Jan 6, 2011 at 10:32 AM, Wolfgang Laun wolfgang.l...@gmail.com wrote: This additional information does not require a fundamentally different approach. Using the same set of facts CostEmployee/CostSpouse, we can now use two rules, one calculating the cost for the employee, and the other one for the spouse. (defrule calc-self ?hbj - (HrBenefitJoin (hrBenefitConfigId 1-73) (calculatedCost 0) {relationshipId == nil} (payingPersonId ?pPer) (coveredPersonId ?cPer) ) (Person(personId?cPer) (dob ?dob) ) (CostEmployee (lo ?lo:(= ?lo (yrs ?dob))) (hi ?hi:(= ?hi (yrs ?dob)))(cost ?cost)) = (printout t Cost for ?cPer paid by himself: ?cost crlf ) ) (defrule calc-other ?hbj - (HrBenefitJoin (hrBenefitConfigId 1-73) (calculatedCost 0) {relationshipId != nil} (payingPersonId ?pPer) (coveredPersonId ?cPer) ) (Person(personId?cPer) (dob ?dob) ) (CostSpouse(lo ?lo:(= ?lo (yrs ?dob))) (hi ?hi:(= ?hi (yrs ?dob)))(cost ?cost)) = (printout t Cost for ?cPer paid by ?pPer : ?cost crlf ) ) The date calculation is abstracted into a (deffunction yrs (?dob) ... (return ?yrs) ) Implementing this in Jess or in Java is a simple programming exercise, but it presumably it depends on some technical/legal issues, e.g., it may not be possible to use today as a basis for calculating the difference in years from the person's dob. Since there's no information what to do with the resulting cost, it's just printed to standard output, but updating slot calculatedCost in HrBenefitJoin is straightforward. Cheers Wolfgang On 05/01/2011, Derek Adams dad...@arahant.com wrote: the facts that I am working with are: (HrBenefitJoin (hrBenefitConfigId 1-73)(benefitJoinId ?bjid)(calculatedCost
Re: JESS: Re: calculating benefit costs
Hi Derek, OK OK... I wasn't going to say anything either but with Peter and James added to Wolfgang, I have to pile too :-) IMHO there are a number of maxims of rule-based programming that you're breaking here. Ernest has put the first one most succinctly in the past: Use many smaller rules that do one thing well rather than one Über Rule that boils the ocean. Declarative programming should say what action(s) are to be performed when certain facts are present, but not attempt to implement those actions directly on the RHS. The second, as James and Peter pointed out, is not to do Java programming on the RHS. If you make the RHS a series of method calls that take the variable bindings from the LHS as arguments, your rules will be much cleaner and maintainable. Finally -- and this is just a plain programming nit -- I noticed the apparent use of magic numbers in your conditions. What about all these calls to overrideAgeCost()? Where are those args coming from? Are they part of some policy? What if that policy changes? Then your rules would be in a dirty form. Wouldn't it be better to get those values from some cache or database with most current values? Something that might help you: I've been collecting rule-based *metaphors* lately -- different ways of thinking about using rules. One metaphor that has been particularly productive has been thinking about digestion and the passing of data through a series of modules like a digestive tract. Obviously, if you carry the metaphor too far ... well you see where the garbage in/ garbage out saying comes into play. :-) But the idea is that you are moving the data through different states, partially processing it each time a new rule module has a crack at it. This has precedent in UNIX/LINUX with pipes -- same idea. Old wine in a new bottle? Perhaps. But this way, you can clearly separate out concerns, add pre and post processing functions, and test partial results by simply disabling/enabling certain modules in the sequence. Cheers, Jason -- Jason Morris Chairman, Rules Fest 2010/2011 http://www.rulesfest.org Morris Technical Solutions LLC consult...@morris-technical-solutions.com (517) 304-5883
RE: JESS: Re: calculating benefit costs
Agree with Peter, And, there might be another solution (others are already mentioned by Wolfgang), which I believe, is a normal practice for other rule-engines available in the current market, providing the concept of Decision Table/Matrix. So, please do the following exercise before writing the rules in JESS language - 1.) Represent this rule into excel sheet as decision table/matrix format (combination of conditions/actions). 2.) Try to find the match in conditions block, and merge it first if there is match in condition(s), so that you can easily find the gaps and duplicates (if there is any). 3.) Try to find how you can normalize this single big table into multiples (if possible), i.e. one table might contain few combined conditions and one or minimum actions, and the condition values could be derived/inferred from other decision table/matrics. Now, once done, 1.) Consider each column of a decision table/matrix as one single rule. As mentioned in #3, you can use JESS *saliance* concept to each rule to set the priority relative to all other rule(s)/rulesets, and using this concept, the less priority rules get the *inferred result* as input from the prioritized ones. 2.) Please write all the combined *if* conditions into the LHS side of each rule. 3.) The RHS will include only the call methods which is setting some value to an object (as per the example), and the inferred result will be input for other rules having less priority. Using this concept, you might have a big list of rules, which is easily understandable/maintainable from user/developer point of view. But still one BIG question in my mind, compare to Wolfgang example or in general, is this really a better approach from rule-engine working memory (or any other) perspective? Regards DEBASISH DALUI (122816) -- Cognizant Technology Solutions US Corp Cell : +1-216-835-2902 From: owner-jess-us...@sandia.gov on behalf of Peter Lin Sent: Fri 1/7/2011 11:58 AM To: jess-users@sandia.gov Subject: Re: JESS: Re: calculating benefit costs I couldn't agree more with james and donald. Having a ton of if/then/else statements in the RHS really should be avoided. One of the benefits of using a rule-centric approach is it helps you see the logic. I find that having deeply nested if/then/else often hides logical flaws that are hard to fix and debug. Think of it like having small simple methods in your java code versus having a gigantic java method with thousands of lines. On Fri, Jan 7, 2011 at 10:35 AM, Donald Paul Winston satchwins...@yahoo.com wrote: So many if else statements on the RHS is unruly. On Jan 7, 2011, at 9:15 AM, Derek Adams wrote: Thanks for the help guys. Here is what I ended up with. This works, but I'm sure it's not the most efficient way to solve the problem. (defrule setCalculatedCostGCI20k5k (HrBenefitJoin (hrBenefitConfigId 1-76)(benefitJoinId ?bjid)(calculatedCost ?cost)(OBJECT ?obj)(coveredPersonId ?cPer)(payingPersonId ?pPer)(relationshipId ?rel)) (Person (personId ?cPer)(dob ?dob)(OBJECT ?objP)) = (printout t age = (call ?objP calcAgeAsOf(call com.arahant.utils.DateUtils now )) for ?bjid crlf) (if (eq ?rel nil) then (if ( (call ?objP calcAgeAsOf(call com.arahant.utils.DateUtils now )) 30) then (call ?obj overrideAgeCost 11.55) else (if ( (call ?objP calcAgeAsOf(call com.arahant.utils.DateUtils now )) 40) then (call ?obj overrideAgeCost 18.95) else (if ( (call ?objP calcAgeAsOf(call com.arahant.utils.DateUtils now )) 50) then (call ?obj overrideAgeCost 38.35) else (if ( (call ?objP calcAgeAsOf(call com.arahant.utils.DateUtils now )) 60) then (call ?obj overrideAgeCost 65.95) else (if ( (call ?objP calcAgeAsOf(call com.arahant.utils.DateUtils now )) 70) then (call ?obj overrideAgeCost 104.35) else (printout t age greater than 69 ... (call ?objP calcAgeAsOf(call com.arahant.utils.DateUtils now )) for ?bjid crlf)) else (if (neq ?rel nil) then (if ( (call ?objP calcAgeAsOf(call com.arahant.utils.DateUtils now )) 30) then (call ?obj overrideAgeCost 4.20) else (if ( (call ?objP calcAgeAsOf(call com.arahant.utils.DateUtils now )) 40) then (call ?obj overrideAgeCost 6.05) else (if ( (call ?objP calcAgeAsOf(call com.arahant.utils.DateUtils now )) 50) then (call ?obj overrideAgeCost 10.90) else (if ( (call ?objP calcAgeAsOf(call com.arahant.utils.DateUtils now )) 60) then (call ?obj overrideAgeCost 17.80