Re: JESS: Re: calculating benefit costs

2011-01-09 Thread Peter Lin
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

2011-01-09 Thread Donald Paul Winston
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

2011-01-08 Thread Wolfgang Laun
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

2011-01-07 Thread Derek Adams
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

2011-01-07 Thread James Owen
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

2011-01-07 Thread Donald Paul Winston
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

2011-01-07 Thread Jason Morris
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

2011-01-07 Thread Debasish . Dalui

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