What does Drools do with ambiguous matches? - drools

I'm playing around with some rules to standardize street addresses that failed look-up from a service provider.
I've defined this rule
rule "Derive Street Aliases"
when
$street : Street();
then
insert( new StreetAlias($street.getPrefixPart() + " " + $street.getStemmedPart(), $street.getName()) );
end
It takes a street name like "South Main Street" and creates the alias "South Main".
This other rule then picks up the alias:
rule "Street Alias Match"
when
$userAddress : UserAddress();
$streetAlias : StreetAlias(alias == $userAddress.getStreetPart());
then
$userAddress.setResolvedAddress($userAddress.getNumberPart() + " " + $streetAlias.getName());
end
This would work fine until a "South Main Avenue" is inserted. Then the alias "South Main" would become ambiguous.
What then is supposed to happen in the 2nd rule? Would it detect a conflict? Would it fire twice so that the last one wins?
I'm interested in the theory of what should happen, as I'm fairly new to Rules Engines.

It is the fundamental principle of the many pattern/many object pattern match problem that the engine should produce all possible matches. In rule based system this means that the engine must explore all possible combinations while evaluate constraints and create activations for each rule, combining facts in various tuples, which will then be used in rule firing.
If you have addresses stored as
class Address {
String streetSpecific;
String streetGeneric;
}
the same "specific" part can be used with several generic parts even in the same city. (Although, duplication of the full street name also occurs.) With an alias
class Alias {
String specific;
}
this rule finds all possible matches
rule "find possibles"
when
Alias( $spec: specific )
Address( streetSpecific == $spec )
then
// another possible match
end
and the rule
rule "find similar"
when
$a1: Address( $spec: streetSpecific )
$a2: Address( this != $a1, streetSpecific == $spec )
then
// display similar pair
end
will find "similar" street names (but note that this rule will fire too often - can you see why?)
Note that even the full name is no guarantee for uniqueness within a single city...

Related

Drools - check if argument is null before calling query

Inside the drools rule file, I'm trying to match the request object against inserted facts using a query (backward chaining). How do I check for null for the request object attribute? If the attribute is not null, I want to pass it to the query. If the attribute is null, I want to keep it unbound so that it will match all results. Since there are many request attributes, I'm looking for a generic solution instead of different rules for each attribute.
To give an example, lets assume I have two attributes currency and country in the goal: Goal() object and I want to call the query isMatching(String country,String currency)
if goal.getCountry() and goal.getCurrency() is not null, I want to call isMatching with goal.getCountry()and goal.getCurrency().
isMatching(goal.getCountry(),goal.getCurrency())
if goal.getCountry() is null and goal.getCurrency() is not null, I want to call isMatching with unbound variable country and goal.getCurrency()
isMatching(country,goal.getCurrency())
if goal.getCountry() is not null and goal.getCurrency() is null, I want to call isMatching with goal.getCountry() and unbound variable currency
isMatching(goal.getCountry(),currency)
if both goal.getCountry() and goal.getCurrency() are null, I want to call isMatching with unbound variable country and currency
isMatching(country,currency)
Best practice is to have a separate rule for each combination.
rule "both country and currency"
when
Goal( $country: country != null, $currency: currency != null )
$isMatching: Boolean() from isMatching( $country, $currency )
then
//
end
Not sure what you're referring to as an "unbound" variable in your question for your other use cases.
If you insist on not following best practices and try to kludge all of this into a single rule, you could either do your null check on the right hand side, or possibly abuse conditional and named consequences to do this. Doing it in the rule consequences ("then") will cause you to lose all of the performance optimization done by the Drools engine, which is done on the left hand side only.
Alternatively you could just update the query to handle the null case.
query isMatching( String $country, String $currency) {
$country := String( this == null )
or
$currency := String( this == null )
or
( actual implementation )
}
rule "example"
when
Goal( $country: country, $currency: currency )
isMatching( $country, $currency )
then
// ...
end
Actual implementation may vary; I have no idea how you'd implement a currency <-> country check.

Drools compare if one list contains element from another list

I have got below structure in Java:
public class Request {
List<Product> product;
List<Account> accounts;
}
public class Product {
String productIdOne;
String productIdTwo;
String productTax;
}
public class Account {
List<ProductRelationship> productsRelationship;
}
public class ProductRelationship {
String productIdOne;
String productIdTwo;
}
And the request is the fact object send to drools. I am wondering how I can check if there is at least one product that productTax is set to 'true' and there is a relationship between one account and one product. In other words, if there is a product with tax set to true and at least one account contains a relationship with this product (by productIdOne and productIdTwo) then the rule result should pass;
The main issue is that the list of the product relationship is inside the account list.
Thanks for any advice
You have a rather straight-forward set of conditions, so it is possible to write a relatively simple rule to check them. I will consider each condition separately and then combine them into a final rule.
As you wrote:
there is at least one product that productTax is set to 'true'
Now, as you mentioned, your rule inputs are the Request instance which contains two lists (products, accounts.) We'll start by declaring that:
rule "Account exists with taxed product"
when
Request( $products: product != null,
$accounts: accounts != null )
Next, we want to find the taxed product. If we only wanted to prove the existence of the product, we could use an exists( ... ) condition, which is extremely fast. However since we want to do further comparisons, we'll want to actuall find the product with this condition and save a reference to it.
$taxedProduct: Product( productTax == "true" ) from $products
I've assumed here that any value other than exactly "true" is indicative of an untaxed product. You should adjust as needed (and possibly consider changing this type to a boolean.)
The next condition is to find the account:
there is a relationship between one account and [the taxed] product
First, we'll need to update our $taxedProduct declaration and get references to its ids:
$taxedProduct: Product( productTax == "true",
$id1: productIdOne,
$id2: productIdTwo ) from $products
Now we need to find an account with a matching relationship.
$account: Account( $relationships: productsRelationship != null ) from $accounts
exists( ProductRelationship( productIdOne == $id1,
productIdTwo == $id2 ) from $relationships )
Here, I used an exists condition for the relationship because we don't need to refer to the relationship itself ever again, just verify that the relationship exists. I did declare a variable $account to point to the account that has the product relationship.
Putting it all together, we have:
rule "Account exists with taxed product"
when
Request( $products: product != null,
$accounts: accounts != null )
$taxedProduct: Product( productTax == "true",
$id1: productIdOne,
$id2: productIdTwo ) from $products
$account: Account( $relationships: productsRelationship != null ) from $accounts
exists( ProductRelationship( productIdOne == $id1,
productIdTwo == $id2 ) from $relationships )
then
// We have a taxed product $taxedProduct
// and an associated account $account
end
When I first started with Drools I found it difficult to wrap my head around the way it treated objects in lists, which is why the ProductRelationship sub-list seems like a tricky issue on its face. What Drools is going to do is iterate through the $products list and find those Product instances that meet our criteria (namely, have productTax == "true".) Once it has found these taxed products, it then similarly goes through the $accounts list and finds all Accounts that meet the criteria (which have a productsRelationship list.) Then for each of those accounts, it is going to test that there exists a relationship as we've defined.
This is a simplified explanation, of course, but it helps to form a mental model of roughly what Drools is doing here. In reality Drools is much more efficient than then roughly O(n^3) workflow I've just described.
An interesting thing you should keep in mind is that this rule is not going to "stop" as soon as it finds a match. If you have two taxed products that have a relationship to a single account, this rule will fire twice -- once for each taxed product. Or, alternatively, if you have one taxed product and two accounts that have a relationship to it, the rule will fire twice (once for each account.) Basically, the rule will fire once for each "match" it finds in the given request.
can you try this following
rule "sample"
no-loop
when
request:Request(accountList: accounts)
request1:Request(productList: product)
Account(productsRelationshipList:ProductRelationship) from accountList
Product(productId contains productsRelationshipList, productTax = true ) from productList
then
System.out.println("Rule fired satisfied");
end

Can I join the column names with search_related in DBIx?

I have a DBIx Class schema where I have;
A Device that has many Interfaces
An Interface has many Rules Applied
Each Rule has many Rule Entries.
I want to search for all of the Rule Entries for a Particular device name and Rule Name.
I am still learning DBIx so I don’t know if this is even the most efficient way.
I am doing this like so;
my $rs = $self->search( { devicename => ‘DeviceA’ } )->search_related('interfaces')->search_related(’Rules’, { rulename => ‘RuleA’ } )->search_related(‘RuleEntries’, {},
{ columns => [qw/source destination port/], result_class => 'DBIx::Class::ResultClass::HashRefInflator'} );
What I am trying to do is get the ‘RuleName’ as a column of my result set.
at the moment I’m getting all of the Rule Entries for DeviceA with a RuleName on an interface called RuleA, The columns returned are
‘source destination port’.
I want this to look like
‘rulename source destination port’
As you are already restricting the rule name it doesn't make sense to query it from the database.
Besides that you should always search for objects of the type you want to get back, in your case that's rule entries:
my $rs = $schema->resultset('Rule_Entries')->search({
'rel_device.name' => 'DeviceA',
'rel_rule.name' => 'Rule',
},{
columns => [ 'rel_rule.name', 'me.source', 'me.destination', 'me.port' ],
join => { rel_rule => { rel_interface => 'rel_device' }},
});
It seems your doing something very similar what I do: storing firewall rules. You might want to have the rule directly related to the device and the interface being an optional attribute of the rule because some vendors don't have interface specific rules (Checkpoint).

Drools - Modify specific object in a list?

I want to know how to modify a object in a list . I tried following, but it gives a error.
when
Category( $bookList : books )
UserProfile( profile == UserProfile.STUDENT )
$book : Book( student == true )
$category : Category( books contains $group )
then
modify( $category.books[$book] ) { setEligible(true) }
end
Book.setEligible is the method i need to call. But i need to call this for selected object in Cagegory.books list. What am i doing wrong ? can anyone help ?
Thanks !
First thing: Are you sure you want to include 2 different Category patterns in your rule? If you have 2 different categories, you may end up with 4 executions of that rule.
Second thing, if you want to modify the book, why don't you just do:
modify($book) {
setEligible(true)
}
Hope it helps,
You also need to make sure that Book is a fact.. that means that you are inserting that Fact into the Ksession..
What is the error that you are getting? which version of drools are you using?
I am modifying an item in a nested collection like this...
[using "from"]
rule "4G complete"
salience -1
when
$tr: TopTowerResult()
$ptncascade: PtnCascade() from $tr.cascadeList
Timestamp() from $ptncascade.cascadeFact.actual4g
then
$ptncascade.getCascadeFact().setComplete4g(true);
$ptncascade.getCascadeFact().setEstimate4g("Completed");
end
Explanation -
This line matches every instance of PtnCascade in $tr.cascadeList
You could add an additional pattern here to limit the selected items from the list.
$ptncascade: PtnCascade() from $tr.cascadeList
The next line operates on every instance of PtnCascade() that was matched.
Timestamp() from $ptncascade.cascadeFact.actual4g
The consequence also operates on the specific PtnCascade that was matched in the list -
$ptncascade.getCascadeFact().setComplete4g(true);

Drools object graph rule definition

I have an object graph that I am trying to generate Fulfillment object from in Drools. Specifically, Fulfillment objects represent a rule that is either satisfied, or unsatisfied. My object graph looks like the following:
Users ---> many Requirements --> Event
`--> many Records ----^
Records can fulfill Requirements if they both point at the same Event. This produces a Fulfillment object in Drools.
A reduce down rule to produce Fulfillments is the following:
rule "fulfils"
when
$u : User()
$rec : Record() from $u.records
$r : Requirement(event contains $rec.event) from $u.requirements
then
insertLogical( new Fulfillment($u, $rec, $r, true));
System.out.println("Inserting logical");
end
rule "unfulfils"
when
$u : User()
$rec : Record() from $u.records
$r : Requirement(event not contains $rec.event) from $u.requirements
then
insertLogical( new Fulfillment($u, $rec, $r, false));
System.out.println("Inserting logical");
end
query "fulfillment"
$fulfillment : Fulfillment()
end
The problem I run into here is if the user has no records, there is no Fulfillment inserted for the requirement. I believe this is because there is no Record() to search on to satisfy my graph.
Is there a way to use the records without requiring more than zero to exist?
Also, do I need two rules here to insert both true and false Fulfillments or is there a better way to do this?
Edit
Another problem I am facing with these rules is the Requirement(event contains $rec.event) does not accomplish the task of finding if any records satisfy the given collection of events. Is there a better way to find if there exists an overlap between the many record's single events, and the single requirements multiple events?
Another Edit
Here's another approach I thought up. Instead of inserting Fulfillments if a requirement/record pair is not found, why not just insertLogical Fullfillments for all Requirements that have no matching positive Fullfillment:
rule "unfulfils"
when
$u : User()
$r : Requirement() from $u.requirements
not(Fulfillment(user == $u, requirement == $r, fulfilled == true))
then
insertLogical( new Fulfillment($u, null, $r, false));
System.out.println("Inserting logical");
end
query "fulfillment"
$fulfillment : Fulfillment()
end
This takes care of the issue of comparing the overlap of two collections, and the case where a user has no records. (Would appreciate some validation on this).
Using 2 different rules for your situation is a common pattern. It makes your rule base easier to read (and in a way to maintain too).
Regarding your question about no Record(), I think you could write something like this (If I understood your question correctly):
rule "unfulfils because of no Record"
when
$u : User(records == null || records.empty == true) //A user without records
$r : Requirement() from $u.requirements // but with Requirements
then
//You don't have a record to set in your Fulfillment object
insertLogical( new Fulfillment($u, $rec, null, false));
System.out.println("Inserting logical");
end