On my OPA team we have a situation in which it would be useful to be able to run a series of calculations on a set of instances sequentially. We're wondering whether or not there is an acceptable method for this within a single call to a rulebase, because OPA does not seem to allow looping or sequential processing of data in a way that is obvious to us. Context:
Consider a situation involving a calculation that can be performed on a single invoice, but we have a set of invoices to process. E.g., let's say there is a certain amount of a deduction or "co-payment" that needs to be subtracted off the invoices, but will be subtracted in greater amounts from invoices received earlier (rather than being distributed equally across them) and then will run out once enough has been subtracted overall. In this case it would not be sufficient to use a statement like "for each of the invoices..." because the conditions of the calculation must change with each invoice. This seems to require the ability to iterate through an ordered list, but we're not sure whether anything like this is possible (without violating an axiom or best practice). Ideas:
The best idea we've been able to come up with would be to use a date/time stamp to identify the earliest invoice as the starting point, and then use the "is known" feature to force the rules to calculate the deduction for "the next earliest" invoice in sequence. The current workaround will be to call the rulebase multiple times through an external script that loops through the invoices.
To sum up, we're wondering whether there is an acceptable approach to apply a set of rules to an entity instance before applying the same rules to other instances, without requiring multiple calls to the rulebase.
Edited by: Patrick Devine on Jul 6, 2011 7:11 PM
You started to get on the right track. To quickly answer the questions, there is no need for multiple calls to the rule base nor for explicit ordering of rules or data.
The challenge when using unordered rules / determinations systems is in breaking away from the procedural thinking (that loops or explicit sequencing of data is necessary). The solution arises when expressing the logical declarative equivalent (that which does not require sequence to conclude the correct answer). In this case, think about the calculation from the perspective of a single invoice at a time and write the rules that correctly express how much to pay, what is the copay, etc. For processing batches in which running tallies are needed, there is a convenient trick to use in rule based systems - conclude the subtotal through each item being tallied (such as remaining annual copay through each invoice). This approach enables declarative rules that "define" how to compute the tally through an invoice so that any subsequent invoices can use the prior invoices tally. Unfortunately the abstract description of the approach always sounds more confusing so here's a pseudo rule example which may communicate the key elements of the approach ...
Invoices have numbers (or date/times) which can be used to conclude an absolute order for all invoices (we'll use Invoice's ID for ordering them in this example)
Person's remaining annual copay is the initial amount of copay remaining before the current batch of invoices is processed
Each invoice should include a $50 copay (for simplicity, although this could be concluded by rules and vary based on services rendered, etc)
Assume all invoices are more than the copay (again for simplicity of this example, although this could be handled within the conditions of rules as well)
The main goals of the rule base are to determine the copay for each invoice and the person's annual copay balance (i.e. copay they may still have to pay this year) after processes a batch of invoices.
Pseudo rules to get the general concept:
Define the order in which invoices should be processed ...
- The invoice (prior invoice) is a member of the invoice's prior invoices if the prior invoice's ID < the invoice's ID
- The invoice (subsequent invoice) is a member of the invoice's subsequent invoices if the subsequent invoice's ID > the invoice's ID
- The invoice (immediately prior invoice) is a member of the invoice's immediately prior invoice IF
the immediately prior invoice is a member if the invoice's prior invoice's AND
the count of all the invoice's prior invoices with an ID > the immediately prior invoice's ID = 0
For concluding the initial remaining copay of each invoice ... put these in a rule table so they can conclude the same attribute but use different logical formulas
- The invoice's initial remaining copay = the person's remaining copay if the count of the invoice's immediately prior invoices is 0 (i.e. it is the first invoice in the batch)
- The invoice's initial remaining copay = the invoice's immediately prior invoice's final remaining copay (i.e. each "initial remaining copay" is the same as the immediately prior invoice's "final remaining copay)
Use a table to define how much copay for each invoice - two possible scenarios ...
- The invoice's copay = $50 if the invoice's initial remaining copay >= $50
- The invoice's copay = the invoice's initial remaining copay if the invoice's initial remaining copay < $50
A rule to determine each invoice's final remaining copay (i.e. after substracting the copay's up through this invoice from the person's annual total)
- The invoice's final remaining copay = the invoice's initial remaining copay - the invoice's copay
A rule to determine the person's final remaining copay (i.e. after all invoices in this batch have been processed)
- the person's final remaining copay = the invoice's final remaining copay if the count of the invoice's subsequent invoices = 0 (i.e. the final remaining copay of the "last" invoice in the batch)
Note some of the above relationships and attributes are not absolutely necessary, (i.e. it isn't necessary to carry forward both an initial remaining copay and determine a final remaining copay per invoice, but doing so keeps the logic of each rule a bit simpler).
Hope that helps ...
The approach described above by Matt makes use of inferred relationships. If you're not familiar with inferred relationships in OPA, then it would be worth reading up on them in the OPM Help. I'd start with:
* Reason about the relationship between two entities: http://download.oracle.com/docs/html/E20340_01/Content/Rules%20using%20entity%20instances/Reason_about_relship_between_2_entities.htm
* InMemberOf function in the entity/relationship function list: http://download.oracle.com/docs/html/E20340_01/Content/Reference/Rule%20syntax%20reference/Entity_and_relationship_functions.htm
I think approaching this sort of logic problem with inferred relationships is the right way to go. They can be tricky if you've never used them before, but once you get comfortable and familiar with them, you'll see how super useful they can be! :D
This is great - thanks very much Matt. It's interesting that we can trade procedural thinking for declarative statements. It hadn't occurred to me to use inferred relationships to this degree of detail (i.e. to define the ordering). This is an important realization because we'd been viewing sequential processing as a "barrier"; now I see that it just requires a reformulation of the logic. I'm glad I asked the question and am grateful for your help.
I remembered this post from a couple of months ago: Can Entites be sorted It was about allocating funds to different instances of an entity according to a priority order (based on an entity-level attribute), until the total funds are exhausted.
The example involves children and pocket money. It's simpler than what you're doing, but my reply to that user included example rules which you could copy and paste and run as an example (to see this sort of allocation logic working in the Debugger).
Anyway, just thought I'd point it out to you in case it's useful.