Optaplanner: trying to understand unwanted pattern rule in nurse rostering example - drools

I'm new to drools and java and I'm trying to understand how this rule from the nurse rostering example works, especially the first part about $pattern.
rule "unwantedPatternShiftType3DaysPattern"
when
$pattern : ShiftType3DaysPattern(
$dayIndex0ShiftType : dayIndex0ShiftType,
$dayIndex1ShiftType : dayIndex1ShiftType,
$dayIndex2ShiftType : dayIndex2ShiftType
)
PatternContractLine(
pattern == $pattern, $contract : contract
)
ShiftAssignment(
shiftType == $dayIndex0ShiftType,
contract == $contract,
$employee : employee, $firstDayIndex : shiftDateDayIndex
)
ShiftAssignment(
shiftType == $dayIndex1ShiftType,
employee == $employee,
shiftDateDayIndex == ($firstDayIndex + 1)
)
ShiftAssignment(
shiftType == $dayIndex2ShiftType,
employee == $employee,
shiftDateDayIndex == ($firstDayIndex + 2)
)
then
scoreHolder.addSoftConstraintMatch(kcontext, - $pattern.getWeight());
end
Specifically, how does drools know what value is in: dayIndex0ShiftType, dayIndex1ShiftType, dayIndex2ShiftType? It calls the ShiftType3DaysPattern class with these values, but how are these values determined?
In addition, when it makes this call:
ShiftType3DaysPattern (dayIndex0ShiftType, dayIndex1ShiftType, dayIndex2ShiftType)
which refers to the following:
#XStreamAlias("ShiftType3DaysPattern")
public class ShiftType3DaysPattern extends Pattern {
private ShiftType dayIndex0ShiftType;
private ShiftType dayIndex1ShiftType;
private ShiftType dayIndex2ShiftType;
public ShiftType getDayIndex0ShiftType() {
return dayIndex0ShiftType;
}
public void setDayIndex0ShiftType(ShiftType dayIndex0ShiftType) {
this.dayIndex0ShiftType = dayIndex0ShiftType;
}
public ShiftType getDayIndex1ShiftType() {
return dayIndex1ShiftType;
}
public void setDayIndex1ShiftType(ShiftType dayIndex1ShiftType) {
this.dayIndex1ShiftType = dayIndex1ShiftType;
}
public ShiftType getDayIndex2ShiftType() {
return dayIndex2ShiftType;
}
public void setDayIndex2ShiftType(ShiftType dayIndex2ShiftType) {
this.dayIndex2ShiftType = dayIndex2ShiftType;
}
#Override
public String toString() {
return "Work pattern: " + dayIndex0ShiftType + ", " + dayIndex1ShiftType + ", " + dayIndex2ShiftType;
}
}
Is this shorthand for ShiftType3DaysPattern.getDayIndex0ShiftType, ShiftType3DaysPattern.getDayIndex1ShiftType, and ShiftType3DaysPattern.getDayIndex2ShiftType?
And if this is the case, how does ShiftType3DaysPattern know which pattern to return if there are more than one "3 day patterns" in the xml source files? What am I missing?
Furthermore, if there are more than one "3 day pattern" then, how does drool automatically apply this rule to all of these "3 day patterns"?

Specifically, how does drools know what value is in: dayIndex0ShiftType, dayIndex1ShiftType, dayIndex2ShiftType? It calls the ShiftType3DaysPattern class with these values, but how are these values determined?
Every ShiftType3DaysPattern in your problem fact collection will be evaluated against this rule in combination with the other conditions in the when clause. So every combination of a ShiftType3DaysPattern, PatternContractLine, and three ShiftAssignments available as problem fact in the Kie session will be evaluated. If all the conditions match (so the PatternContractLine matches the ShiftType3DayPattern and all three ShiftAssignment types are assigned in the order of the unwanted ShiftType3DayPattern, the rule will be fired and the score will be impacted negatively. So the value of dayIndex0ShiftType, dayIndex1ShiftType and dayIndex2ShiftType will be whatever is set in the ShiftType3DaysPattern in the problem fact collection.
Is this shorthand for ShiftType3DaysPattern.getDayIndex0ShiftType,
ShiftType3DaysPattern.getDayIndex1ShiftType, and
ShiftType3DaysPattern.getDayIndex2ShiftType?
I'm not sure what you are referring to exactly, but if you are referring to this:
$pattern : ShiftType3DaysPattern(
$dayIndex0ShiftType : dayIndex0ShiftType,
$dayIndex1ShiftType : dayIndex1ShiftType,
$dayIndex2ShiftType : dayIndex2ShiftType
)
This is just syntax for assigning dayIndex0ShiftType to the variable $dayIndex0ShiftType so it can be referred to in the rule later. The pattern itself is also assigned to a variable $pattern. The dollar sign itself is just a convention.
And if this is the case, how does ShiftType3DaysPattern know which pattern to return if there are more than one "3 day patterns" in the xml source files? What am I missing?
Furthermore, if there are more than one "3 day pattern" then, how does drool automatically apply this rule to all of these "3 day patterns"?
As I said before, the rule will be evaluated for every problem fact combination available, so every ShiftType3DaysPattern will be evaluated against the other facts stated in the when clause of the rule.
I recommend you to read the Drools documentation, it will help you to improve your basic understanding of Drools. It is a long read, but will be worth it. At least read the User guide chapter.

Related

Drools - how do I compare a string value present in a Java Object which is present inside a List

I am new to Drools and am having a tough time writing rules
Here is my data structure :
public class Premium{
private List<InsuranceType> insuranceTypes;
}
public class InsuranceType {
private String name;
}
So a Premium object will contain a List of Insurance types and I need to check if any of the insurance types has a name of "TPD"
Have tried the following :
rule "rule#3"
when
$fact:Premium($insuranceTypes : InsuranceType(name == 'TPD'))
then
System.out.println("Error");
end
However app server fails to start with the following error:
2021-11-30 12:16:37.004 ERROR 23500 --- [ main]
o.d.c.k.builder.impl.AbstractKieModule : Unable to build KieBaseModel:defaultKieBase
Unable to Analyse Expression InsuranceType(name == "TPD"):
[Error: unable to resolve method using strict-mode: com.xyz.Premium.name()]
[Near : {... InsuranceType(name == "TPD") ....}]
^
[Line: 29, Column: 5] : [Rule name='rule#3']
Unable to analyze expression 'InsuranceType(name == "TPD")' : [Rule name='rule#3']
Field Reader does not exist for declaration '$insuranceTypes' in '$insuranceTypes :
InsuranceType(name == "TPD")' in the rule 'rule#3' : [Rule name='rule#3']
I'm going to assume there's a public getName method on the InsuranceType class, and a public getInsuranceTypes method on the Premium class. If either of those isn't true, you need to add either getters or make those properties public.
Your rule was pretty close. However the problem you have is that insuranceTypes is a list but you were treating it as an object.
You have several options here, depending on your needs. However I'd go with the simplest, which is this:
rule "Example"
when
Premium( $insuranceTypes: insuranceTypes )
exists( InsuranceType( name == "TPD" ) from $insuranceTypes )
then
System.out.println("Error");
end
In the first line, I get the insurance types and assign them to the variable $insuranceTypes. This variable is now the list of types.
Then in the second line, I assert that there exists at least one InsuranceType in the list that has the name "TPD".
Note that Drools also has a memberOf operator, and a contains operator, which come in useful when working with lists and other iterable collections. These are inverses of each other, eg. you'd do Example( foo memberOf $someList ) or Example( myList contains $something ).

Drools: Get identifier from LHS pattern

I am using Drools 6.3.0 Final. Assuming I have a rule like this
rule "Child of Person over 18"
when
$person : Person(age > 18)
$child : from $person.children
then
end
Let us further assume I build my KieSession with this rule, add some facts and now I want to know the identifiers used in all rules / in all rules that matched my facts.
So what I want to get here is $person and $child.
I know I can get the rules, which fired using an AgendaEventListener and from the event I can get the name of the rule, as well as the objects for $person and $child. But I fail to find a way to get the identifiers $person and $child from my match. Using a debugger I can see the information is there... actually the Rule I get from the event, is a RuleImpl, which has a lhsRoot, in which I can find that information... but this sounds much more complicated than it should be and not the intended way.
So I was wondering if there is not a better way for this.
Your requirement can be easily achieved by using Drools' public API. You were looking at the right place (AgendaEventListener) but Match.getObjects() is not what you need. What you need is a combination of Match.getDeclarationIds() (to get the list of identifiers) and then Match.getDeclarationValue(String id) (to get the value for each identifier). As an example, this is how an AgendaEventListener that logs this information in the console will look like:
import org.kie.api.event.rule.BeforeMatchFiredEvent;
import org.kie.api.event.rule.DefaultAgendaEventListener;
...
ksession.addEventListener(new DefaultAgendaEventListener() {
#Override
public void beforeMatchFired(BeforeMatchFiredEvent event) {
String ruleName = event.getMatch().getRule().getName();
List<String> declarationIds = event.getMatch().getDeclarationIds();
System.out.println("\n\n\tRule: "+ruleName);
for (String declarationId : declarationIds) {
Object declarationValue = event.getMatch().getDeclarationValue(declarationId);
System.out.println("\t\tId: "+declarationId);
System.out.println("\t\tValue: "+declarationValue);
}
System.out.println("\n\n");
}
});
As #laune mentioned,you can also get an instance of the match that activated a rule in the RHS of the rules themselves. In this case, the Match object is accessible through drools.getMatch().
Hope it helps,

In Drools, does memberOf / contains require the tested object to be inserted as fact explicitly?

I am still trying to wrap my head around some basics of drools.
One thing I don't understand is to what level I have to explicitly add facts into a session vs. declaring something as a fact or (how is it different?) as a variable within a rule.
Question: Do I always have to explicitly add a fact or does drools see a declared variable somehow as a fact as well?
Say I have this simplified hiearchy:
public class Container {
private Collection<Element> elements;
// other stuff
}
public class Element {
private SubElement subElement;
// other stuff
}
public class SubElement {
private code;
// other stuff
}
Now I want to find/match a container that contains Elements with a SubElement of a given code.
This is a rule I created:
global Collection qualifyingCodes;
rule "container rule"
when
// find a container
$container : Container( $elements : elements )
// where there is an element within its elements list
$element : Element(this memberOf $elements)
// that has a sub element
$subElement : SubElement(this == $element.subElement)
// for which the code is in the (global) list of codes
eval(qualifyingCodes.contains($subElement.code))
then
....
Now it seems to work fine, but only if I add the container, the elements and subelements separately as facts into my session.
I was hoping to be able to just add the container object as a fact and due to the "declared" $elements/$element/$subElement drools would understand those as facts as well.
Is there a way to do this (or do I always have to flatten out my data structures and add those as separate facts)?
Thanks for any advice!
POJOs inserted as facts can be matched with a pattern without the need for establishing a context (as your rule illustrates very well).
It is, however, possible to create a context where a limited set of POJOs that may or may not have been inserted as facts can be matched by a pattern: via the from phrase. Here's essentially the same rule, but without Element and SubElement being facts:
rule "from rule"
when
// find a container
$container : Container( $elements : elements )
// where there is an element within its elements list
$element : Element($subEl: subElement ) from $elements
// that has a sub element
$subElement : SubElement(this == $element.subElement, data == "DEF")
from $subEl;
then
System.out.println( "found Container " + $container +
" for " + $subElement.getData());
end
But, besides being visible in a general context, insertion as a fact has another consequence as well: facts can be modified with the engine being aware of that modification. In the "from rule", a modify of $subElement isn't possible, and so any modification setting that SubElement's data to "DEF" will not make this rule fire (not even if you insert the SubElement POJO).
For completeness sake: reasoning with a hierarchy of objects (composition) is also simplified by adding the references to the parent object, e.g.
rule "linkage rule"
when
// a sub element GHI
$subElement : SubElement( data == "GHI", $element: element)
then
System.out.println( "found Container " + $element.getContainer() +
" for " + $subElement.getData());
end

Can I not use contains operator on a collection assigned to a variable?

I would appreciate it if someone could explain to me why this is illegal:
rule "some rule name"
when
$a : A($bset : bset)
$bset contains B(x == "hello")
then
//do something
end
Where:
public class A {
private Set<B> bset = new HashSet<B>();
//getters and setters for bset
//toString() and hashCode for A
public static class B {
private String x
//getters and setters for x
//toString() and hashCode() for B
}
}
The error from the Drools eclipse plugin is not very helpful. It provides the following error:
[ERR 102] Line 23:16 mismatched input 'contains' in rule "some rule name"
The error appears on the line with "bset contains..."
I have searched through the Drools documentation, as well as a book that I have, and have not found the examples to be very illustrative in this regard.
'contains' is an operator that must be used inside a pattern. $bset contains B(x == "hello") is not a valid pattern in this case.
There are a couple of ways to achieve what you are trying to do. This is one of them:
rule "some rule name"
when
$a: A($bset : bset)
$b: B(x == "hello") from $bset
then
//you will have one activation for each of the B objects matching
//the second pattern
end
Another:
rule "some rule name"
when
$a: A($bset : bset)
exists (B(x == "hello") from $bset)
then
//you will have one activation no matter how many B objects match
//the second pattern ( you must have at least one of course)
end
If you want to see how contains operation is used, and if the B objects are also facts in your session, you can write something like this:
rule "some rule name"
when
$b: B(x == "hello")
$a: A(bset contains $b)
then
//multiple activations
end
or:
rule "some rule name"
when
$b: B(x == "hello")
exists( A(bset contains $b) )
then
//single activation
end
Hope it helps,

How to check null date condition in drools?

I have to check a condition on date whether a date field of a entity is in range of another two sets of date from other entity
First Entity :
1. id
2. name
3. date
Second Entity ;
1. id
.
.
.
.
17 : Start Date
18 : End Date
I have to check whether the date field of first entity is in range of Start Date and End Date of second entity.
e.g.
(t1.date>= t2.Start Date and t1.date <= t2.End Date)
Problem is that, there are some row where t2 is null.. if it is null, then second condition return true.
My Attempt
PriorAuthorizationLogVO( cardid == $paidClaimVO.cardholderId, ruleType == 'INCL' , $paidClaimVO.time>=etime , (ttime==null || (ttime!=null && $paidClaimVO.time<=ttime))
But, i am not able to confirm whether it is working....
Please help.
You could add that check for date between the range of dates in either a static helper method or within one of your entities. In my opinion this will make your rules more readable and you can easily write unit tests for the date check method.
Option 1 - Static helper method
Create a class for having static helper methods, something like this
public class DateUtils {
private DateUtils() {} // Cannot be initialized
public static boolean dateInRange( Date toCheck, Date min, Date max ) {
// Add null checks here
// Use Date#before(Date) and Date#after(Date) for checking
return true|false
}
}
Your rule would then be like
import static DateUtils.*
rule "date in range"
when:
dateInRange( e1.date, e2.start, e2.end )
then:
// logic
end
Option 2 - Method within fact/entity
Create the check method inside one of your facts. In which entity to put this depends on your use case, the information you've given does not specify this yet. I think you can figure out the best place by yourself. Anyway, the code would be something like this
public class Entity1 {
Date date
}
public class Entity2 {
Date start
Date end
public boolean entity1InRange( Entity1 e ) {
// null checks
// Use the Date#before() and Date#after() as above
}
}
And the rule
rule "date in range"
when:
e2.entity1InRange( e1 )
then:
// Logic
end