Matching and Compliance Engine
Intro
The 3B Compliance engine is a set of tools that allow system admins to define rules, conditions and restrictions that will apply on shifts that are booked as well as used as part of matching for relevant candidates
Look Back and Look Forward Extension
The lookBackExtendDays and lookForwardExtendDays allows us to load define a few days before/and after the start and end field paths. This is useful when creating compliance rules that need to evaluate multi-day hour calculations. Usually these default to the value “1”
Compliance Rules
Compliance rules are Javascript based code rules that evaluate shifts and return evaluation results.
Compliance rules are created as records against the Scheduling Compliance Rule custom metadata.
A compliance rule has the following footprint:
async ({ event, events, contactId, loaders, EvaluationResult }) => {
return []
}
As you can see, there are a number of parameters made available to your compliance rule:
- event - this is the contextual event that is being evaluated. Usually a shift, but it can also be an employee request
- events - these are all of the loaded events in memory. This is where the lookBackExtendDays and lookForwardExtendDays come in handy, as you can load more data than displayed on the scheduler
- contactId - this is the contact’s id for evaluation. So, if we are trying to match a shift to a number of candidates, the rule will be re-ran for each candidate and the contactId will be the candidate’s id
- Loaders - this will be an array of loader objects that allow you to access additional data. The matchingLoaders property on the shift schedulable is an array where you can define the names of additional loaders
- EvaluationResult is a class that you must use to return the matching results from the result
Compliance Rule Loaders
You can create custom loaders and pass the loader data to compliance rules by specifying the loader name in the matchingLoaders array property
Example loader that loads certificates
{
name: "certificates",
objectName: "b3o__Certificate__c",
filterRecordsBy: function () {
return "b3o__Contact__c IN (SELECT b3o__Candidate__c FROM b3o__Placement__c WHERE b3o__Job__r.b3o__Client_Account__c = {0})"
},
groupRecordsBy: "b3o__Contact__c",
fieldToLoad: ['b3o__Certificate_Type__c', 'b3o__Contact__c', 'b3o__Start_Date__c', 'b3o__End_Date__c', 'b3o__Revoke_Certificate__c'],
filteringItems: function () {
return [
`'${contextRecordId}'`,
]
}
}
Compliance Rule EvaluationResult
EvaluationResult is an object that you can use to return structured compliance/evaluation results. The following properties need to be passed:
- event/events - an array or a single instance of a calendar event. We will create the Event Alert records against the event/events passed in this property
- detail - longer description
- category - grouping category
- points - positive/negative whole integer points assignment for evaluation result
return [
new EvaluationResult({
events: allWorkerShifts,
detail: `⚠️ Too many booked hours today (${totalDailyHours})`,
category: 'Working Time',
points: -30,
}),
];
Complete Examples
//Example 1 - check for shift/availablility overlap
async ({ event, events, contactId, loaders, EvaluationResult }) => {
if (!contactId) {
console.warn('No contact id for evaluation');
return [];
}
const unavailableRequests = events.filter(
(e) =>
e.extendedProps.b3s__Contact__c === contactId &&
e.title === 'request' &&
e.extendedProps.b3s__Type__c === 'Unavailable',
);
const availableRequests = events.filter(
(e) =>
e.extendedProps.b3s__Contact__c === contactId &&
e.title === 'request' &&
e.extendedProps.b3s__Type__c === 'Available',
);
const checkOverlap = (shift, requests) => {
return requests.some((request) => {
const shiftStart = new Date(shift.extendedProps.b3s__Scheduled_Start_Time__c).getTime();
const shiftEnd = new Date(shift.extendedProps.b3s__Scheduled_End_Time__c).getTime();
const requestStart = new Date(request.extendedProps.b3s__Start__c).getTime();
const requestEnd = new Date(request.extendedProps.b3s__End__c).getTime();
return (
//Request starts within shift
(requestStart >= shiftStart && requestStart <= shiftEnd) ||
//Request ends within shift
(requestEnd >= shiftStart && requestEnd <= shiftEnd) ||
//Request within shift times
(requestStart >= shiftStart && requestEnd <= shiftEnd) ||
//Shift within request times
(shiftStart >= requestStart && shiftEnd <= requestEnd)
)
});
};
if (checkOverlap(event, unavailableRequests)) {
return [
new EvaluationResult({
event: event,
detail: '🏝️ Not available',
category: 'Availability',
points: -50,
}),
];
} else if (checkOverlap(event, availableRequests)) {
return [
new EvaluationResult({
event: event,
detail: '💪 Asked for shifts',
category: 'Availability',
points: 10,
}),
];
} else {
return [
new EvaluationResult({
event: event,
detail: '😎 Available',
category: 'Availability',
points: 10,
}),
];
}
}
Invitations
The invitations schema is used to determine how Shift Invites are interpreted within the scheduler. The below code sample is the default state that can be overridden, by adding an “invitations” object to the “shift” schedulable within the Scheduler Definition. The schema allows admins to define the object that will be used for managing invitations as well as the following special properties:
- canBook - this is a selector which determines when the “Book” button appears against an active invitation. By default, the Book button is visible only for Accepted invitations. You can modify this behaviour as necessary
{
"schedulables": {
//...
"shift":{
//...
"invitations": {
"objectName": "b3s__Invitation__c",
"contactField": "b3s__Contact__c",
"contactRecordReference": "b3s__Contact__r",
"shiftField": "b3s__Shift__c",
"expiresField": "b3s__Expires__c",
"statusField": "b3s__Status__c",
"createdDateField": "CreatedDate",
"canBook": {
"condition": {
"allOrSome": "some",
"selectors": [
{
"field": "b3s__Status__c",
"operator": "equals",
"value": "Accepted",
},
],
},
},
"title": {
"contactName": "Name",
"contactTitle": "Title",
"contactDescription": "Description",
},
"recordHistoryData": [
{
"dateFieldPath":"CreatedDate",
"label":"Invited",
"isComplete":{
"condition":{
"allOrSome":"all",
"selectors":[
{
"field":"CreatedDate",
"operator":"isnull",
"value":false
}
]
}
}
},
{
"dateFieldPath":"LastModifiedDate",
"label":"Accepted",
"isSuccess":true,
"isComplete":{
"condition":{
"allOrSome":"all",
"selectors":[
{
"field":"b3s__Status__c",
"operator":"equals",
"value":"Accepted"
}
]
}
},
"isVisble":{
"condition":{
"allOrSome":"all",
"selectors":[
{
"field":"b3s__Status__c",
"operator":"equals",
"value":"Accepted"
}
]
}
}
},
{
"dateFieldPath":"LastModifiedDate",
"label":"Revoked",
"isError":true,
"isComplete":{
"condition":{
"allOrSome":"all",
"selectors":[
{
"field":"b3s__Status__c",
"operator":"equals",
"value":"Revoked"
}
]
}
},
"isVisble":{
"condition":{
"allOrSome":"all",
"selectors":[
{
"field":"b3s__Status__c",
"operator":"equals",
"value":"Revoked"
}
]
}
}
},
{
"dateFieldPath":"LastModifiedDate",
"label":"Rejected",
"isError":true,
"isComplete":{
"condition":{
"allOrSome":"all",
"selectors":[
{
"field":"b3s__Status__c",
"operator":"equals",
"value":"Rejected"
}
]
}
},
"isVisble":{
"condition":{
"allOrSome":"all",
"selectors":[
{
"field":"b3s__Status__c",
"operator":"equals",
"value":"Rejected"
}
]
}
}
},
{
"dateFieldPath":"LastModifiedDate",
"label":"Booked",
"isSuccess":true,
"isComplete":{
"condition":{
"allOrSome":"all",
"selectors":[
{
"field":"b3s__Status__c",
"operator":"equals",
"value":"Booked"
}
]
}
},
"isVisble":{
"condition":{
"allOrSome":"all",
"selectors":[
{
"field":"b3s__Status__c",
"operator":"equals",
"value":"Booked"
}
]
}
}
},
{
"dateFieldPath":"b3s__Expires__c",
"label":"Expires",
"isComplete":{
"condition":{
"allOrSome":"all",
"selectors":[
{
"field":"b3s__Expires__c",
"operator":"isnull",
"value":false
}
]
}
},
"isVisble":{
"condition":{
"allOrSome":"all",
"selectors":[
{
"field":"b3s__Expires__c",
"operator":"isnull",
"value":false
},
{
"field":"b3s__Status__c",
"operator":"equals",
"value":"New"
}
]
}
}
}
]
},
}
}
}
When Compliance Runs?
The compliance engine executes whenever a shift/request change is made on the scheduler. Users can also manually re-calculate compliance by using the button on the right side pop-down bar.
Search and Match Shifts
Users can initiate shift matching from the “Match” context menu option. This menu option is conditionally rendered based on the canMatch conditional property.
Once the user selects on the “Match” option, they will see a pop-up modal with a list of results of potential candidate matches.
Each candidate is ordered based on merit and a Relative % score is provided. Please note that the relative % score doesn’t mean that someone is fully qualified, but it measures the candidate’s score compared to all other candidates.
Each candidate will be presented with their Name, Description and a list of EvaluationResults, grouped by Category with total points per category. Any positive points will be in green and any negative points will be in red.
A user can then invite one or a number of candidates by clicking on the top right-hand corner of the candidate card.
The candidate will then be able to see any invitations in the mobile app, and interact with them to accept or reject the invitations.
The selectionClause on the invitation schedulable in the Clock In/Out definition controls which invitations are visible, so you can for example hide invitations that are expired, revoked, invalid.
When the candidate accepts the invitation (Status = Accepted), the book button will be made available on the invitation card.
You can control when the Book button is displayed based on the canBook property (search this document).
If the user chooses to Book the candidate, the shift will be assigned to the selected candidate, completing the flow.
Tips:
- Use 3B Push Notifications to push shift alerts to candidates using Flows in addition to Emails.
- If you need to implement a F3 process (fastest finger first), you can use a Flow to auto-assign the shift to the Invitation’s Contact, completing the workflow, without requiring that a user approves the acceptance
- Note, you might need to also develop a Flow that will block anyone else from accepting an invitation to a shift that is already booked
Compliance on Shifts
Compliance on shifts is displayed as an indicator on top of the shift. If a user places their mouse over the indicator, they can see the full list of results. The counter on the shift is only for the negative point evaluation results.
Suitabilities
Candidate suitabilities are used to link Candidates to Account, Site or Job Type in order to teach the system if that candidate has a positive or negative preference to the specific Account, Site or Job Type. So, for example, if a client has a positive impression of a candidate, system users can create a Suitability record that links the candidate and the client account with an Excellent rating. This will have a positive impact on the score the candidate will get when we search for a suitable candidate for a shift.
Likewise, a negative rating will have a negative impact on the candidate in any search and match executions.
Developers can extend this behaviour by adding custom lookups and modifying the custom rule that evaluates the candidate.
Certificate Requirements
Certificate Requirements link Certificates to Account, Job Type or Site in order to instruct the system which certificates are required for a shift. So, if a Shift is linked to a Job that is for a “Java Developer” in the site “IBM Office”, you can create two Certificate Requirement records that link the “Java Development Experience” certificate to the Job Type “Java Developer” and the “IBM Office Induction” to the Site IBM Office.
This setup will ensure that when we have a shift for evaluation/compliance, we will get consistent results of potential candidates / compliance results that are contextual and users don’t need to manually specify what certificates are required, as the system will use the implicit relationship between the certificates and the Job Type/Account/Site.
Similar to Suitabilities, developers can extend this behaviour by adding custom lookups and modifying the underlying code rules to link to an ATS, custom object model or even modify points assignment.