I need to count the number of facts satisfying a certain constraint in working memory and then fire the rule if the number of facts breaches a certain threshold. I tried the following but it is complaining about "mismatched input '$cnt' in rule "Rule1"
rule Rule1
when
accumulate ( AFact ( code == "XXXX" ); $cnt : count(1) ; $cnt > 1 )
then
// fire
end
Any help to get the syntax right would be appreciated. Thanks!
The accumulate Conditional Element (as you are using) is only available in Drools versions 5.4 and later - you must be using 5.3.0 or earlier.
You can use the old form of accumulate, available as a continuation of from:
Number( intValue > 1 )
from accumulate( AFact ( code == "XXXX" ), count(1) )
Related
Using Drools 5.5.FINAL. I have a rule that detects when there is exactly one ACTIVE Contract record from among multiple Contract records in working memory.
/**
* There is exactly ONE Active Contract
*/
rule "Exactly one ACTIVE among multiple"
salience 870
activation-group "BRValidation"
no-loop
when
$bmsContract : BMSContract( STAT_CD == BMS_Contract_Status.ACTIVE.toString() )
not BMSContract( this != $bmsContract, STAT_CD == BMS_Contract_Status.ACTIVE.toString() )
$blueReport : BlueReport( status == null )
then
logger.info("Rule detected one ACTIVE record from among multiple contracts;" + " Rule=" + drools.getRule().getName());
modify ( $blueReport ) {
setStatus( BlueReportStatus.SUCCESSFUL.toString() ),
setReason( "Single ACTIVE status among many contracts - that's OK"),
appendRuleAudit(drools.getRule().getName());
}
end
What I need to do in the RHS is to remove all instances of BMSContract object except for the one that has "ACTIVE" STAT_CD. How can I do that?
I came across some legacy hql. This query is taking around 150 ms . As you can see the code is quite complex and almost unreadable not to mention the performance issue.
select distinct pub.id, ret.sector.id, ret.poiSector.id, ret.id, man.id, 0
from Publisher pub
left join pub.retailer ret
left join pub.manufacturer man
where (
pub.id in (
select publisher_id from Store AS s
where s.lat >= 52.297382 and s.lat <= 52.746616 and s.lng >= 13.047624 and s.lng <= 13.785276
group by 1
having min( sqrt( pow(111.3*(s.lat - 52.522), 2) + pow(67.7*(s.lng - 13.416), 2) ) ) is null or min( sqrt( pow(111.3*(s.lat - 52.522), 2) + pow(67.7*(s.lng - 13.416), 2) ) ) < 25
)
or
(man is not null)
)
and (ret is null or ret.hidden is false)
group by 1, 2, 3, 4, 5, 6
I am planning to break up the code segments as well as trying to modify this to increase performance . My question is how can I break this in an efficient way ? will this help in some way ? I dont have full visibility of the system so this are the information I can give you for now .
I am a particular situation on my hands that I do not what to call it, so I am just going to explain it! I am working on File-maker pro database for a university that keeps track of students that are majoring in education one of the tables keeps track of all of the standardized test taken by students that range from the following; ACT, SAT, ETS, and Papa. before when I made this table, all test stood alone or in other words you couldn't combine scores to make a passing grade. That has now changed with two test, PAPA and ETS. In order words, you can combine scores from each test to make one grade to pass.
Here is an example
lets just say for simplicity sake that you need a grand total of 10 for the PAPA and ETS test in order to pass those individual test
Test Name | Reading | Writing | Math | Total | pass/fail
--------------------------------------------------------
PAPA | 2 | 2 | 2 | 6 | FAILED
ets | 1 | 2 | 6 | 9 | FAILED
So A couple weeks ago.. this individual would not pass there standard test, but now this indivdual would hve passed because he/she has gotten a a 6 in the ETS Math and two 2's in Reading and writing in the PAPAS.
Here's what my current calculation looks like:
If (
ReadingScore ≥ BasicTest::PassReading
and MathScore ≥ BasicTest::PassMath
and WritingScore ≥ BasicTest::PassWriting ;
"PASSED" ;
If ( Score >= BasicTest::QMScore ;
If ( ReadingScore >= BasicTest::QMReading ;
If ( MathScore >= BasicTest::QMMATH ;
If ( WritingScore >= BasicTest::QMWriting ;
"PASSED" ;
"DOES NOT MEET WRITING REQUIREMENTS"
) ;
"DOES NOT MEET MATH REQUIREMENTS"
) ;
"DOES NOT MEET READING REQUIREMENTS"
) ;
"FAILED"
)
)
So my question here today is how should I approach this with the Filemaker pro application? Should I make this a report or should a just rebuild the table completely?!?!?!?
While I'm not sure exactly how your calc matches up with your question (I think Score in your calc is the total of the other scores?), I think I can offer some pointers.
Given what you say and what the calc shows, I think the following are true:
Given multiple tests, you're interested in the maximum score for each category.
Each maximum score needs to be greater than the minimum score from either BasicTest::Pass* or BasicTest::QM*.
The test taker passes if all their max scores are greater than all the test minimum scores or the total max scores is greater than the BasicTest::QMScore.
Given the above, I came up with the following calc:
Let (
[
_reading_score = Max ( papa_reading_score ; ets_reading_score ) ;
_math_score = Max ( papa_math_score ; ets_math_score ) ;
_writing_score = Max ( papa_writing_score ; ets_writing_score ) ;
_total_score = _reading_score + _math_score + _writing_score ;
_target_reading = Min ( BasicTest::PassReading ; BasicTest::QMReading ) ;
_target_math = Min ( BasicTest::PassMath ; BasicTest::QMMath ) ;
_target_writing = Min ( BasicTest::PassWriting ; BasicTest::QMWriting ) ;
_pass_reading = ( _reading_score ≥ _target_reading ) ;
_pass_math = ( _math_score ≥ _target_math ) ;
_pass_writing = ( _writing_score ≥ _target_writing ) ;
_passed = ( _pass_reading and _pass_math and _pass_writing )
or ( _total_score ≥ BasicTest::QMScore );
_result = Case (
_passed ; "PASSED" ;
not _pass_reading ; "DOES NOT MEET READING REQUIREMENTS" ;
not _pass_math ; "DOES NOT MEET MATH REQUIREMENTS" ;
not _pass_writing ; "DOES NOT MEET WRITING REQUIREMENTS" ;
"FAILED"
)
] ;
_result
)
Even if the above isn't exactly what you need, I hope it'll provide some direction. With a complicated calc like this, break it up into pieces with each piece (in this case, each variable assignment) being easy to understand.
Note that Max and Min can take many arguments and can also take fields that have multiple values (related fields in a one-to-many relationship or repeating fields), so if you have more than two scores or two tests to check for maximum and minimum values, just add them as more parameters.
I am using FMP 14 and would like to auto-populate field A based on the following calulation:
If ( Get ( ActiveLayoutObjectName ) = "tab_Visits_v1" ; "1st" ) or
If ( Get ( ActiveLayoutObjectName ) = "tab_Visits_v2" ; "2nd" ) or
If ( Get ( ActiveLayoutObjectName ) = "tab_Visits_v3" ; "3rd" ) or
If ( Get ( ActiveLayoutObjectName ) = "tab_Visits_v4" ; "4th" ) or
If ( Get ( ActiveLayoutObjectName ) = "tab_Visits_v5" ; "5th" ) or
If ( Get ( ActiveLayoutObjectName ) = "tab_Visits_v6" ; "6th" )
The above code is supposed to auto-populate the value 1st, 2nd, 3rd ... in field A depending on the name of the object the Get (ActiveLayoutObjectName) function returns. I have named each of my tabs, but the calculation is only returning 0.
Any help would be appreciated.
thanks.
The way your calculation is written makes very little sense. Each one of the If() statements returns a result of either "1st", "2nd", etc. or nothing (an empty string). You are then applying or to all these results. Since only of them can be true, your calculation is essentially doing something like:
"" or "2nd" or "" or "" or "" or ""
which happens to return 1 (true), but has no useful meaning.
You should be using the Case() function here:
Case (
Get ( ActiveLayoutObjectName ) = "tab_Visits_v1" ; "1st" ;
Get ( ActiveLayoutObjectName ) = "tab_Visits_v2" ; "2nd" ;
Get ( ActiveLayoutObjectName ) = "tab_Visits_v3" ; "3rd" ;
Get ( ActiveLayoutObjectName ) = "tab_Visits_v4" ; "4th" ;
Get ( ActiveLayoutObjectName ) = "tab_Visits_v5" ; "5th" ;
Get ( ActiveLayoutObjectName ) = "tab_Visits_v6" ; "6th"
)
Note also that a calculation field may not always refresh itself as a result of user switching a tab. This refers to an unstored calculation field; if you are trying to use this as the formula to be auto-entered into a "regular' (e.g. Text) field, it will never update.
Added:
Here is our situation. We see a patient a maximum of 6 times. We have
a tab for each one of those 6 visits.
I would suggest you use a portal to a related table of Visits instead of a tab control. A tab control is designed to display fixed components of user interface - not dynamic data. And certainly not data split into separate records. You should have only one unique record for each patient - and as many records for each patient's visits as may be necessary (potentially unlimited).
If you like, you can use the portal rows as buttons to select a specific visit to view in more detail (similar to a tab control, except that the portal shows the "tabs" as vertical rows). A one-row portal to the same Visits table, filtered by the user selection, would work very well for this purpose, I believe.
With 1. .... it would be easy:
Right (Get ( ActiveLayoutObjectName ) ; 1) & "."
Thanks for pointing out, that my first version does not work.
I'm using Drools (for the first time) to express some rules and it has been working really well so far. However I've been given a new condition that I'm not able to express in the rules language very clearly.
Essentially I need to perform an action on the players account if they have an outstanding balance on there account between a certain amount, where they haven't made a payment in the last week and where they haven't made a payment in the last 4 weeks that is greater than or equal to a weekly deduction. There are a few other rules but I've removed them in an effort to simplify the rule for this question. It's the last rule that is causing me a problem.
rule "The broken rule"
salience 10
no-loop
when
Player( $playerNumber : playerNumber )
$a : Account( // balance between £5 and £100 and no arrangement
playerNumber == $playerNumber &&
accountBalanceInPence >= 500 &&
accountBalanceInPence <= 10000
)
not ( // no payment in last week
exists AccountTransaction(
playerNumber == $playerNumber &&
transactionDate >= oneWeekAgo &&
transactionCode == "P" // payment
)
)
/* It's this next bit that is broken */
not ( // no payment > (weekly cost * 4) paid within last 4 weeks
$deduction : AccountTransaction( // a recent transaction
playerNumber == $playerNumber &&
transactionDate >= fourWeeksAgo &&
transactionCode == "D" // deduction
)
exists AccountTransaction( // the payment
playerNumber == $playerNumber &&
transactionDate >= fourWeeksAgo &&
transactionCode == "P" // payment
amountInPence >= ($deduction->amountInPence * 4)
)
)
then
// do some action to the account
end
The problem is that it just doesn't work, I keep getting org.drools.rule.InvalidRulePackage exceptions thrown. I was just guessing on the syntax but couldn't seem to find an example that showed what I'm trying to do. Is it even possible?
The full original error message is:
"unknown:50:3 mismatched token: [#255,1690:1695='exists',<39>,50:3]; expecting type RIGHT_PAREN[54,4]: unknown:54:4 mismatched token: [#284,1840:1852='amountInPence',<7>,54:4]; expecting type RIGHT_PAREN[54,22]: unknown:54:22 Unexpected token '$payment'"
After trying the suggestion in the first comment the error is:
"[50,3]: unknown:50:3 mismatched token: [#255,1690:1695='exists',<39>,50:3]; expecting type RIGHT_PAREN[54,4]: unknown:54:4 mismatched token: [#284,1840:1852='amountInPence',<7>,54:4]; expecting type RIGHT_PAREN[54,45]: unknown:54:45 mismatched token: [#293,1881:1881='*',<71>,54:45]; expecting type LEFT_PAREN[55,3]: unknown:55:3 mismatched token: [#298,1890:1890=')',<12>,55:3]; expecting type THEN"
yes as you guessed, you need to put an explicit "and" inside the "not" pattern to join them together.
The only time you don't need the "and" is at the top level:
eg
when Foo() Bar()
Doesn't require a an "and"
but this is implicitly the same as
when Foo() and Bar()
So your solution seems correct. The lack of a top level "and" seems to be convention in most rule languages (going back to CLIPS !)
After some more hacking around the following doesn't cause any runtime errors (though I'm not sure if it's "correct" yet). I rewrote the clause to put the exists around both the facts and used an infix and to group them.
not ( // no payment > (weekly cost * 4) paid within last 4 weeks
exists (
AccountTransaction( // a recent transaction
playerNumber == $playerNumber &&
transactionDate >= fourWeeksAgo &&
transactionCode == "D" // deduction
$recentDeducation : amountInPence
) and
AccountTransaction( // the payment
playerNumber == $playerNumber &&
transactionDate >= fourWeeksAgo &&
transactionCode == "P" // payment
amountInPence >= ($recentDeducation * 4)
)
)
)
Thanks for all the help so far.
What about ($deduction->amountInPence * 4)? I think, the -> should be a . instead.