Tuesday, June 18, 2019

Get Next Work, Get Most Urgent

System settings


GetNextWork_SkipSkillCheck

If you know that your application doesn't require skills or uses them rarely, set this to 'enabled' so that the SQL can skip all the complicated JOINS on the operator and assignment skill tables.

GetNextWork_SkipSkillCount

By default, if an assignment lists more than one skill, the user must possess ALL of these skills in order to be able to receive the assignment. When you set this to 'enabled', it changes the logic to ANY of these skills. Changing this setting to enabled also improves performance slightly because the query has to perform fewer comparisons.

GetNextWork_SkipUnskilledAssignments

By default, an operator who has skills is entitled to receive EITHER the assignments for which he has the skill(s) to perform, OR assignments that don't specify any skills at all. However, you may want your skilled workers to focus on receiving assignments that require their skills; therefore, set this to 'enabled' when you want that functionality.
This is a good setting to override in a production ruleset and set to 'disabled' when the unskilled operators are being overwhelmed, so that the skilled operators can pitch in Kanban-style.

GetNextWork_UseLegacyGetContent

This setting is rarely used. It has the skill-checking SQL fall back to the original RIGHT-JOIN logic it used to apply in PRPC 6.x. SkipSkillCount doesn't work with it. This option is retained in case its needed for some database for which the original logic runs faster.

pyGetNextWork_UseAdditionalIndexGetContent

The pyErrorAssignment IS NULL check causes Oracle to do a table scan. To get the index to be used, create an index for the pc_assign_workbasket table on the nvl function and set this setting to true, which will change the query to AND nvl(wb0.pyerrorassignment, 'null') ='null' instead.

GetNextWork_MoveAssignmentToWorklist

This is the default way we allow 'cycling' through a workbasket. Every time you click Get Most Urgent, it pulls the assignment out of the workbasket and into your worklist. However, sometimes you want to keep the workbasket assignments right where they are. Disabling this will do so and it introduces an additional JOIN on the system locks table to make sure that the assignment you are performing is not locked by anybody else (which otherwise would be a major performance problem due to lock contention.)

GetNextWork_WorkBasketUrgencyThreshold

This is the default urgency threshold if you leave that field blank in the Operator ID configuration. The default value is set to zero (0); therefore, this setting has no impact. But if it was set to 51 and the Operator ID specified workbaskets A, B, and C, where B is set to threshold 76 but the others are left blank, then the system first looks for assignments with urgency 51 to 100 in workbasket A, then urgency 76 to 100 in workbasket B, and then urgency 51 to 100 in workbasket C. If none are found, the system looks at urgency 0 to 50 in workbasket A, urgency 0 to 75 in workbasket B, and finally urgency 0 to 50 in workbasket C.

Configuration

You specify the GetNextWork settings on the Operator ID form and use the logic of the GetNextWorkCriteria decision tree.

GetNextWork settings on the Operator ID form

You choose most of the Get Next Work system settings on the Operator ID form:



Workbaskets are checked for the next assignment to perform in the order listed (by default).
Urgency Threshold, as explained in the GetNextWork_WorkBasketUrgencyThreshold system setting, introduces a second pass concept. Therefore, with this system setting applied, GetNextWork operates as follows:
  1. Assuming the system setting is 0, it runs a query for AccountException assignments 0 to 100. (That is, it gets the first 500 records or whatever the list view maximum value specifies. 500 records is the default value.)
  2. If none of the AccountException assignments pan out, it runs a query for the first 500 in AdminProtocolWB between urgencies 51 to 100.
  3. If none of the 51+ AdminProtocolWB assignments yield results, it runs a query for the 0 to 50 AdminProtocolWB assignments.
There easily could have been a 501st assignment that would have worked for AccountException, causing the system to avoid going on to AdminProtocolWB. Therefore, the MaxRecords setting in the listview is important to get right.
However, if GetNextWork is testing 500+ assignments routinely, performance will be slow. The workbaskets should be listed on the Operator ID in the order of most likely assignments that this operator can work on.
Get from workbaskets first will usually be checked. If this setting is not checked, the system gets the highest urgency assignment in your worklist first.
Merge workbaskets, when checked, causes the Urgency Threshold column to be ignored because all of the assignments in the listed workbaskets are equally viable. The system retrieves all of the assignments in all of the listed workbaskets, sorted by urgency.
When Merge workbaskets are checked, an additional check box shows up: Use all workbasket assignments in user's workgroup. This setting hides the entire workbaskets grid because it compares the assignment's workgroup to the current user's workgroup.
Get Next Work in currentWorkPool (true/false) is an additional parameter to the GetNextWork activity that allows Get Most Urgent to only pull assignments from whatever workpool the portal is currently set to. In the post PRPC 5.5.x world of composite portals, the concept of a current work pool is deprecated because all workpools in the application should be equally viable.
Find Get Next Work in currentWorkPool in the Action tab of Rule-Obj-FlowAction.

Here you can see that when back-to-back processing is not occurring, rather than show the Confirm harness, you can call Get Next Work in two flavors, either normally or by current workpool. (Note that this calls Work-.getNextWorkObject, not Work-.GetNextWork) You can also directly configure the Get Most Urgentbutton, which calls GetNextWork directly - to pass in the currentWorkPool parameter if it is needed.

GetNextWorkCriteria decision tree logic

Finally, you finish specifying GetNextWork settings by determining whether the assignment the system has retrieved is viable or not. For that, after opening the assignment, you pass it to the GetNextWorkCriteria decision tree:
By default, this Pega-provided functionality operates as follows:
IsReadyToBeWorkedOn
If the assignment has an SLA defined, the top section of the SLA rule determines whether an assignment is ready or not:
  • Immediately
  • Define From A Property
  • Interval from assignment creation
HasBeenUpdatedByMeToday
A user feels no need to work on an assignment that he or she has already done some work on today. This is, in fact, how we allow cycling through a worklist. The user clicks Get Most Urgent, gets an assignment off of his worklist, realizes he doesn't want to complete it today, performs a local action of some kind on it, and then -- when he clicks GetMostUrgent again, the system does not retrieve that assignment because the user updated it today.
Important The user needs to select Save as a local action. Clicking the Save button in the application will not work.
CurUserHasRequiredSkills
This check is entirely redundant with what the initial query does, unless GetNextWork_SkipSkillCheck is set to enabled.
You can customize this decision tree.

Cycling through a worklist or workbasket

A user expects that each time he clicks the Get Most Urgent button, he receives a new assignment. However, if GetNextWork is configured to retrieve assignments off of the user's worklist first and it always gets the highest urgency assignment, you assume that it would always show the same assignment each time the user clicks the Get Most Urgent button. Our expectation is that the user clicks the button, gets an assignment off of his worklist, and then takes a flow action on that assignment, either a local action or a connector.
For example, if the assignment is a task or an action that he wants to perform later, he might simply perform a Save of the local action. Later, in the section of this article that explains the GetNextWorkCriteria decision tree, you'll see that the decision tree filters out assignments that were updated by the user today. Therefore, when the user clicks the Get Most Urgent button again, that high-urgency assignment is filtered out in the selection process and the next most urgent assignment will be picked.
For workbaskets, natural cycling develops each time the user clicks the Get Most Urgent button because the query on workbaskets won't ever find an assignment that’s in a user’s worklist. However, if GetNextWork_MoveAssignmentToWorklist is disabled, the workbasket assignments are left in the workbasket. Now you have the same expectation as you have the worklist assignments; namely, the user is expected to perform an operation on the assignment before clicking the Get Most Urgent button to get another urgent assignment.
Important The user needs to select Save as a local action. Clicking the Save button in the application will not work.

Inner workings

The main entry point activity is Work-.GetNextWork. The first thing you'll notice is that this activity is not a Final rule. This is by design so that you and other application developers can customize it. The core workings of GetNextWork are encapsulated in Work-.getNextWorkObject, which is a Final rule.

Work-.GetNextWork

Input Parameters: TargetFrame, currentWorkPool, WorkBasket
TargetFrame is passed to the primary output activity, Work-.Perform.
currentWorkPool is described above in the Configuration section.
WorkBasket is a rarely used parameter that allows the call to GetNextWork to ignore the Operator ID settings and just focus on retrieving the most urgent assignment from the workbasket provided.
Output Parameter: AccessGroupName
AccessGroupName is not needed for the caller to actually check because GetNewWork handles switching access groups as needed. (You'll learn about this next in Processing.)
Processing: This activity is meant to be called directly from the user interface. It first calls getNextWorkObject to arrive at the right assignment for this user to receive, and then it calls activities to display it.
It is possible for GetNextWork to reveal that there aren't any assignments found for the current user to work on.
If this occurs, Step 2 specifies a special screen indicating this. Otherwise, GetNextWork will show the assignment found in its Perform harness. (Note that the actual harness name to show comes from the assignment data itself, in pxFormName).
However, the ruleset of the assignment’s work class may not exist in the current access group, but may be present in another access group that the user has available. If this condition exists, Steps 3 through 5 handle this by using RedirectAndRun to switch to the right access group.

Work-.getNextWorkObject

Input Parameters: workPage, WorkBasket, currentWorkPool, OutputPageName, OutputPageClass, returnProcessStateInfo
workPage is simply the name to use for the work page, usually pyWorkPage.
WorkBasket and currentWorkPool are described under GetNextWork.
OutputPageNameOutputPageClass, and returnProcessStateInfo are standard Process Engine API parameters. In nearly all cases, taking the defaults will be adequate. The output page will be pyOutput and it will use the class Code-ProcessOutput.
Output Parameters: CurrentUserHasOwnership, AccessGroupName
GetNextWork uses AccessGroupName to determine whether it needs to call RedirectAndRun.
The consumer of CurrentUserHasOwnership is actually FinishAssignment, which called this activity by PerformB2BAssignmentCheck. It uses that to determine whether it has an assignment to display to the user.
Processing: Whereas GetNextWork, the graphical user interface activity, is involved with the display of the assignment (or lack of assignment), this activity has the global responsibility to fetch that assignment, and if asked to, move it to the user's worklist. It will call FindAssignmentInWorklist first or last, depending upon the setting on the Operator ID form. This activity is also called by PerformB2BAssignmentCheck, if GetNextWork or GetNextWork in Current Workpool is selected in the flow action After This Action section.
Note that both of the activities findAssignmentInWorklist and findAssignmentInWorkBasket are set to Available = Yes.  While it would be desirable to keep them Final due to the considerable logic within them, this allows customers to do something completely different with GetNextWork.

Work-.findAssignmentInWorkBasket

Input Parameters: The same as getNextWorkObject
Output Parameters: assignmentFound, AccessGroupName
assignmentFound is used by GetNextWorkObject to determine whether it successfully found an assignment for this user or not and the value feeds the other activity's return value of CurrentUserHasOwnership.
Processing: Here is the heart of the GetNextWork processing. The most important thing to realize is that only ONE of Steps 4, 5, or 6 is going to run.
Step 4 is going to bypass whatever is set on the Operator ID and simply look for an assignment in one given workbasket, without regard to an urgency threshold.
Step 5 is going to either get all of the assignments in the workgroup, or it is going to look at the workbaskets listed on the Operator ID form, using them as all equivalent candidates and again tossing out the urgency threshold.
Step 6 is the most involved of the three steps; so let's take a closer look.



The code in Step 6 makes two passes through the list of workbaskets on the Operator ID form (potentially). In the first pass, the urgency thresholds are honored. In the second pass, only the assignments below the listed urgency thresholds are obtained.
But what makes Step 6 even more interesting is that it tries to be backwards compatible. If you overrode the GetNextWork List View prior to upgrading to Pega 7.1, then when you upgraded, you don't have the latest Pega 7.1 changes.
Prior to Pega 7.1.4, you could run a report (which defaulted to 500 records retrieved) against the listed workbasket. Then, if none of those assignments panned out down to the threshold value, it stored the list. In the second pass, the list for that workbasket is retrieved and continues on down in urgency order to see if one could be selected for this user (more on the selection process later).
In Pega 7.1.4, the functionality was altered to use the urgency threshold in the filter criteria. But this requires that filter criteria to be present in the List View as well. Therefore, Step 6 iterates through the assignments during the first pass. If an assignment's urgency drops below the threshold value, we know that you overrode the List View and the processing reverts to pre-Pega 7.1.4 behavior.
Step 6 also handles attempts to deliberately make use of the threshold in the filter criteria that narrows down the result set and thus provides fast queries.
For example, you might configure GetNextWork as follows and as shown in the following screen:
Workbasket
Urgency Threshold
AccountException
95
AccountException
85
AdminProtocolWB
51

In this case, the administrator might be aware that a lot of the assignments (5000+) in workbasket AccountException are at urgency 85 and up. Therefore, to break that up, he turns it into two queries (only if necessary). In this case, all assignments in AccountException with urgency 95 to 100 are retrieved. Only if that doesn't produce a candidate is a second query run to retrieve assignments with urgency 85 to 94. Then, if that doesn't produce a candidate and AdminProtocolWB at 51 to 100 doesn't find anything, the query loops back around to AccountException 0 to 84 and then finally to AdminProtocolWB 0 to 50.

Assign-WorkBasket.GetNextWork list view

The following screen shows how the Content for Assign-WorkBasket * GetNextWork * ALL is specified.
Criteria A, B, or C are all separate modes; it was called for a workbasket, a workgroup, or a workpool. Criteria D and E are the urgency threshold minimum and maximum values from Step 6 of findAssignmentInWorkBasket.
You can override the List View and add your own filter criteria. Note that the maximum retrieved is 500.If the selection criteria (explained below) negates the first 500 assignments retrieved, but the 501st assignment would have suited you, the system won’t repeat the query in a paging manner. GetNextWork won't ever see that one. If you are routinely running through 500+ assignments for a user that don't yield results, this will probably cause significant performance problems anyway. This is why it is incumbent upon you, as the application developer who sets up GetNextWork, to make sure that the listed workbaskets have a high percentage of assignments that the user can actually take.

getContentForGetNextWork

This activity has one Java step to build the SQL. It starts out building the SQL from the filter criteria; then it adds the following:
  1.    The pyErrorAssignment is null check. This is removed from the filter criteria because if the pyGetNextWork_UseAdditionalIndexGetContent system setting is enabled, it will change that to using the NVL function, which also requires a custom index for the database administrator to create.
  2.   Makes sure that the current user has the skills to perform the assignment, if GetNextWork_SkipSkillCheck is disabled. This SQL is optimized above the old RIGHT JOIN, which is still accessible from GetNextWork_UseLegacyGetContent. First, it gets a count of the skills that the assignment requires. Then it does a LEFT JOIN from the assignment skills table to the operator skills table (as opposed to the RIGHT JOIN the other way around) to find the matching skills and makes sure that the skill count matches the JOIN count. In other words, the RIGHT JOIN makes sure that the operator has all the skills the assignment calls for and is more expensive than the LEFT JOIN + COUNT check. If GetNextWork_SkipSkillCount is enabled, the logic for matching the count is skipped so that the ALL skills required becomes an AND skills required.
  3. Assuming GetNextWork_SkipUnskilledAssignments isn't enabled, it then also selects the unskilled assignments.
  4. Finally, if GetNextWork_MoveAssignmentToWorklist is false, a Not In clause is added into the SQL to filter out assignments of locked work objects. This is necessary because moving assignments out of the workbasket and into the user's worklist is the main way to avoid lock contention issues. When that isn't desired, we have to compensate by adding the Not In clause, although this degrades performance. A future release will correct the need for this negative tradeoff.

Candidate assignment selection

Once findAssignmentInWorklist or findAssignmentInWorkBasket has retrieved an assignment, it calls the rule-utility-function pzOpenAssignmentForGetNextWork to determine if the assignment can be used.
The function uses the following selection criteria:
  1. The assignment has to open properly.
  2. It can't be an assignment of an internal system flow, for example, OverallSLA.
  3. The GetNextWorkCriteria decision tree must evaluate it with a result of true. (See the details and the screen below.)
  4. The user must be able to lock the work object.
If a successful candidate is found and it is a workbasket assignment, then the MoveToWorklist activity is called to reassign the assignment to the current user, unless GetNextWork_MoveAssignmentToWorklist is disabled.

Criterion 3, GetNextWorkCriteria decision tree must evaluate Ready To Be Worked On with a result of true

The following screen shows the decision tree logic for Criterion 3:
You  can override this decision tree and add your own content. But the Pega-provided functionality checks that the assignment is ready to be worked on (the readiness comes from the SLA rule), whether this user has already worked on it today, and whether the user has the required skills.

Tracing

In the Tracer Settings, the section Event Types to Trace, specify the Event Type Process Engine:
Select the Process Engine check box:
In fact, the best way to trace GetNextWork behavior is to have ONLY that event checked.

If you click Line 2 in the trace shown above, you can see the full SQL used as well as the prepared values:

Indices

The filter conditions on the Operator Assignment table probably make an index unnecessary because it narrows down the query candidates to just what is on a person's worklist. However, using the Pega-provided functionality, you can take advantage of the following Index properties:
Index name: GetNextWorkFromList
Properties: pxObjClass, pxAssignedOperatorID, pyErrorAssignment, pxUrgencyAssign (sort by)
It probably doesn't get picked, but we haven't tested this because we've never had a performance problem with this table. On the other hand, we've had extensive performance problems on the workbasket table.
We've finally come up with an index that we've verified does get picked by all flavors of GetNextWork in all the major databases (Oracle, SQL Server, DB2), but it only has in it a partial list of the columns specified in the Where Clause. It is the following index:
Index name: PC_Assign_WB_GetNextWork
Properties: pxAssignedOperatorID, pxUrgencyAssign
The index GetNextWorkFromList applies to the pc_assign_worklist table but might be unnecessary, whereas the index PC_Assign_WB_GetNextWork applies to the pc_assign_workbasket table and is very important.

3 comments:

CSV format string from a Pagelist

In our business logic often we have the requirement to convert PageList to string CSV values. From -  PageList.pxResults(1).pyLabel  = P1 Pa...