I'm using the employee scheduling quick start on optapy's github page. Only difference is I modified the Employee class to have the employee's home address included.
@optapy.problem_fact class Employee: name: str skill_set: list[str] address: str
def __init__(self, name: str = None, skill_set: list[str] = None, address: str = None):
self.name = name
self.skill_set = skill_set
self.address = address #new
def __str__(self):
return f'Employee(name={self.name})'
def to_dict(self):
return {
'name': self.name,
'skill_set': self.skill_set,
'address': self.address
}`
For example Instead of LOCATIONS = ["Ambulatory care", "Critical care", "Pediatric care"]
as locations, each shift has a certain patient address as a location which caregivers can visit. From the original repository, the employees are matched to shifts based on their job expertise and availability as well as other constraints, but I would like to add the additional constraint of employees being matched to caregivers by the shortest distance to the patient as well.
I was thinking of using google maps API to retrieve the shortest possible route between two addresses:
import googlemaps
api_key = 'API_KEY'
def find_distance_and_duration_of_route(origin, destination, mode='driving'):
gmaps = googlemaps.Client(api_key)
route = gmaps.directions(origin, destination, mode=mode)
return route[0]['legs'][0]['distance']['text']
I am looking for a way to incorporate this function into the existing constraints in optapy, so that I can match employees to shifts based on the shortest distance, or maybe a completely new solution by your suggestion.
I tried creating a constraint at constraints.py such as:
def closest_employee_to_shift(constraint_factory: ConstraintFactory):
return constraint_factory.for_each(Shift).join(Employee,
Joiners.equal(lambda shift: shift.location,
lambda employee: employee.address)
) \
.reward('Employee is too far from the shift', HardSoftScore.ONE_SOFT,
lambda shift, employee: find_distance_and_duration_of_route(shift, employee))
but unfortunately it didn't work.
Any suggestions on how to implement this would be greatly appreciated. Thank you.
I would precompute a distance matrix that contains distances from employee's addresses to shift location:
@optapy.problem_fact
class DistanceInfo:
distance_matrix: dict
def __init__(self, distance_matrix):
self.distance_matrix = distance_matrix
def get_distance(self, from_location, to_location):
return self.distance_matrix[from_location][to_location]
distance_matrix = dict()
visited_locations = set()
for shift in shift_list:
for employee in employee_list:
if (shift.location, employee.address) not in visited_locations:
if shift.location not in distance_matrix:
distance_matrix[shift.location] = dict()
if employee.address not in distance_matrix:
distance_matrix[employee.address] = dict()
distance_matrix[shift.location][employee.address] = find_distance_and_duration_of_route(shift.location, employee.address)
distance_matrix[employee.address][shift.location] = find_distance_and_duration_of_route(employee.address, shift.location)
visited_locations.add((shift.location, employee.address))
visited_locations.add((employee.address, shift.location))
distance_info = DistanceInfo(distance_matrix)
and then you would use it in a constraint (after adding it to your @planning_solution
like so:
def closest_employee_to_shift(constraint_factory: ConstraintFactory):
return (
constraint_factory.for_each(Shift)
.join(DistanceInfo)
.penalize('Employee distance to shift', HardSoftScore.ONE_SOFT,
lambda shift, distance_info: distance_info.get_distance(shift.employee.address, shift.location))
)