Advanced guides

Mapping Skills

Our API uses a simplified skill logic primarily based on OR conditions and exclusive NOT conditions. Below, you will find detailed instructions and examples on how to adapt your system’s skill requirements to fit our model.

Understanding our model

Remember that skills on a spot and an employee are a list of OR integers. We will only assign an employee to a shift if the spot of the shifts has an overlap with the skills.

  • OR Conditions: If an employee possesses any one of the listed skills, they can be assigned to the shift.
  • NOT Conditions: Employees listed under NOT conditions are explicitly excluded from the shift, regardless of their skills. This is called disliked_employees.
{
  employees: [
    {
      id: 1,
      skills: [1, 2, 3],
      // Omitted other fields
    }
  ],
  spots: [
    {
      id: 10,
      skills: [1, 2],
      // Omitted other fields
    }
  ],
  shifts: [
    {
      spot: 10,
      dislikedEmployees: [1],
      // Omitted other fields and inherits skills from spot
    }
  ],
  settings: {
    useDislikedEmployee: { // This must be present if you do complex skill mapping
      firstPriorityWeight: 1,
      secondPriorityWeight: 0,
      thirdPriorityWeight: 0,
      fourthPriorityWeight: 0
    }
  }
}

Mapping Your Skills to Our Skills

When mapping your skills to our model, you need to represent complex skill requirements within our simplified structure.

Handling AND Conditions

For example, if a spot requires an employee to have two specific skills (an AND condition), you will need to represent this in our model using a Cartesian product (or cross join). This means creating combinations of skills as separate entries in the skills list.

Handling Exclusions

Similarly, if a spot requires an employee to not have a certain skill (an exclusion), you can use our dislikedEmployees field or adjust the spot’s skills list accordingly.

Our system primarily works with OR conditions, so you’ll need to decompose more complex logic into a series of OR conditions and exclusions to fit our model.

A complex example

Here is an example based on a complex skill set requirement

(Skill A AND Skill B) OR (Skill C AND NOT Skill A)

Step 1: Assign integer values to all skills and combined skill

Assume:

  • Skill A (integer 1)
  • Skill B (integer 2)
  • Skill C (integer 3)
  • Skill A AND Skill B (integer 4, a new unique integer representing the combination)

Step 2: JSON Representation

Here is how to represent it in our format:

{
  "employees": [
    {
      "id": 1,
      "skills": [1, 2, 4] // Represents Skill A, Skill B, and the combination Skill A AND Skill B
      // Omitted other fields
    },
    {
      "id": 2,
      "skills": [3], // Skill C
      // Omitted other fields
    },
    {
      "id": 3,
      "skills": [1, 3], // Skill A and Skill C
      // Omitted other fields
    }
  ],
  "spots": [
    {
      "id": 10,
      "skills": [4, 3], // Represents Skill A AND Skill B (4) and Skill C (3)
      // Omitted other fields
    }
  ],
  "shifts": [
    {
      "spot": 10,
      "dislikedEmployees": [3], // Employee 3 has Skill A, hence should be excluded
      // Omitted other fields and inherits skills from spot
    }
  ],
  "settings": {
    "useDislikedEmployee": { // This must be present if you do complex skill mapping
      "firstPriorityWeight": 1,
      "secondPriorityWeight": 0,
      "thirdPriorityWeight": 0,
      "fourthPriorityWeight": 0
    }
  }
}

Step 3: Pseudo-code to guide you

# Define a mapping of skills to integers
SKILL_MAP = {'Skill A': 1, 'Skill B': 2, 'Skill C': 3}

# Define a mapping for combined skills to unique integers
COMBINED_SKILL_MAP = {'Skill A AND Skill B': 4}

# Function to map a skill combination to a unique integer
def mapCombinedSkills(skill1, skill2):
    combination = f"{skill1} AND {skill2}"
    if combination in COMBINED_SKILL_MAP:
        return COMBINED_SKILL_MAP[combination]
    else:  # Generate a new unique integer for the new combination
        new_id = max(COMBINED_SKILL_MAP.values()) + 1
        COMBINED_SKILL_MAP[combination] = new_id
        return new_id

def transformToApiModel(employees, spots, shifts):
    transformedData = {
        'employees': [],
        'spots': [],
        'shifts': [],
        'settings': {'useDislikedEmployee': {
            'firstPriorityWeight': 1,
            'secondPriorityWeight': 0,
            'thirdPriorityWeight': 0,
            'fourthPriorityWeight': 0,
        }},
    }

    # Transform employees
    for employee in employees:
        transformedEmployee = {'id': employee['id'], 'skills': []}
        for skill in employee['skills']:
            if skill in SKILL_MAP:
                transformedEmployee['skills'].append(SKILL_MAP[skill])
        if 'Skill A' in employee['skills'] and 'Skill B' in employee['skills']:
            combinedSkillId = mapCombinedSkills('Skill A', 'Skill B')
            transformedEmployee['skills'].append(combinedSkillId)
        transformedData['employees'].append(transformedEmployee)

    # Transform spots and create a mapping of spot IDs to disliked employees
    spot_to_disliked = {}
    for spot in spots:
        transformedSpot = {'id': spot['id'], 'skills': []}
        dislikedEmployees = []

        for skill in spot['skills']:
            if ' AND ' in skill:
                skill1, skill2 = skill.split(' AND ')
                combinedSkillId = mapCombinedSkills(skill1.strip(), skill2.strip())
                transformedSpot['skills'].append(combinedSkillId)
            elif 'NOT' in skill:
                skill_required, skill_excluded = skill.split(' NOT ')
                if skill_required.strip() in SKILL_MAP:
                    transformedSpot['skills'].append(SKILL_MAP[skill_required.strip()])
                for employee in employees:
                    if SKILL_MAP.get(skill_excluded.strip()) in [SKILL_MAP[s] for s in employee['skills']]:
                        dislikedEmployees.append(employee['id'])
            else:
                if skill.strip() in SKILL_MAP:
                    transformedSpot['skills'].append(SKILL_MAP[skill.strip()])

        transformedData['spots'].append(transformedSpot)
        spot_to_disliked[spot['id']] = dislikedEmployees

    # Transform shifts
    for shift in shifts:
        transformedShift = {
            'spot': shift['spot'],
            'dislikedEmployees': spot_to_disliked.get(shift['spot'], [])
        }
        # Add any additional shift-specific fields here
        transformedData['shifts'].append(transformedShift)

    return transformedData

# Example usage:
employees = [
    {'id': 1, 'skills': ['Skill A', 'Skill B']},
    {'id': 2, 'skills': ['Skill C']},
    {'id': 3, 'skills': ['Skill A', 'Skill C']}
]

spots = [
    {'id': 10, 'skills': ['Skill A AND Skill B', 'Skill C NOT Skill A']},
    {'id': 11, 'skills': ['Skill B', 'Skill C']}
]

shifts = [
    {'spot': 10},
    {'spot': 11},
    {'spot': 10}  # Another shift for spot 10
]

transformedData = transformToApiModel(employees, spots, shifts)
print(transformedData)

>>> {'employees': [{'id': 1, 'skills': [1, 2, 4]}, {'id': 2, 'skills': [3]}, {'id': 3, 'skills': [1, 3]}], 'spots': [{'id': 10, 'skills': [4, 3]}, {'id': 11, 'skills': [2, 3]}], 'shifts': [{'spot': 10, 'dislikedEmployees': [1, 3]}, {'spot': 11, 'dislikedEmployees': []}, {'spot': 10, 'dislikedEmployees': [1, 3]}], 'settings': {'useDislikedEmployee': {'firstPriorityWeight': 1, 'secondPriorityWeight': 0, 'thirdPriorityWeight': 0, 'fourthPriorityWeight': 0}}}
Previous
Retail and call-centers