def save_changed_jobs(self, changed_jobs=[]): # return self.load_workers_original(start_day) # def load_workers_original(self,start_day = '20191101'): db_session = self.cnx # This bulk update does not update secondary workers. db_session.bulk_update_mappings( Job, changed_jobs, ) # db_session.flush() db_session.commit() for job in changed_jobs: job_to_update = JobPlanningInfoUpdate( code=job["code"], planning_status=job["planning_status"], scheduled_start_datetime=job["scheduled_start_datetime"], scheduled_duration_minutes=job["scheduled_duration_minutes"], scheduled_primary_worker_code=worker_service.get( db_session=db_session, worker_id=job["scheduled_primary_worker_id"]).code, scheduled_secondary_worker_codes=[ worker_service.get(db_session=db_session, worker_id=_id).code for _id in job["scheduled_secondary_worker_ids"] ], ) job_service.update_planning_info(db_session=self.cnx, job_in=job_to_update)
def dispatch_jobs(self, env, rl_agent=None): # Real batch, rl_agent is skilpped. self.kandbox_env = env GENERATOR_START_DATE = datetime.strptime( self.kandbox_env.config["env_start_day"], config.KANDBOX_DATE_FORMAT) GENERATOR_END_DATE = GENERATOR_START_DATE + timedelta( days=self.kandbox_env.config["nbr_of_days_planning_window"]) current_date = GENERATOR_START_DATE # day_seq = int(self.kandbox_env.env_encode_from_datetime_to_minutes( self.kandbox_env.data_start_datetime) / 1440) - 1 while current_date < GENERATOR_END_DATE: """ visit_cust = self.kandbox_env.load_job_status_df( planner_code = config.ORIGINAL_PLANNER, start_date = start_date, end_date = end_date ) """ print("current_start_day", current_date, "GENERATOR_END_DATE", GENERATOR_END_DATE) current_start_day = datetime.strftime(current_date, config.KANDBOX_DATE_FORMAT) day_seq = int( self.kandbox_env.env_encode_from_datetime_to_minutes( current_date) / 1440) # purge job status for this planner and this day. workers = self.kandbox_env.workers # start_day=current_start_day # TODO, do it per day jobs_orig = [] for j in self.kandbox_env.jobs: if (j.requested_start_minutes >= day_seq * 1440) and (j.requested_start_minutes < (day_seq + 1) * 1440): jobs_orig.append(j) if len(jobs_orig) < 1: print("it is empty, nothing to dispatch!") return current_shifts = jobs_orig current_workers = workers # .T.to_dict().values() print({ "loaded day": current_date, "job count": len(current_shifts), "current_workers count": len(current_workers), }) worker_day = self.dispatch_jobs_1day( jobs=current_shifts[0:53], workers=current_workers) # [0:20] [:70] # worker_day is the dispatched result, now we save it into DB. if len(worker_day) < 1: print( "no data returned from opti1day! I will move on to next day!" ) current_date = current_date + timedelta(days=1) continue # pprint(worker_day) job_list = [] for w_i in range(len(worker_day)): pre_job_code = "__HOME" for task in worker_day[w_i][:-1]: task_id = task[0] # + 1 worker_code = current_workers[w_i].worker_code # updated_order = {} # latest_order_dict[id] job_list.append({ "job_code": current_shifts[task_id].job_code, "job_schedule_type": current_shifts[task_id].job_schedule_type, "planning_status": JobPlanningStatus.IN_PLANNING, "scheduled_primary_worker_id": worker_code, "scheduled_start_day": datetime.strftime(current_date, config.KANDBOX_DATE_FORMAT), "requested_start_day": datetime.strftime(current_date, config.KANDBOX_DATE_FORMAT), "scheduled_start_minutes": task[1] + (day_seq * 1440), "scheduled_duration_minutes": current_shifts[task_id].requested_duration_minutes, "scheduled_travel_minutes_before": task[2], "scheduled_travel_prev_code": pre_job_code, # "location_code": current_shifts[task_id]["location_code"], "geo_longitude": current_shifts[task_id].location[0], "geo_latitude": current_shifts[task_id].location[1], "conflict_level": 0, "scheduled_secondary_worker_ids": "[]", "scheduled_share_status": "N", "error_message": "", }) pre_job_code = current_shifts[task_id].job_code """ import pprint pprint.pprint(job_list) return """ # TODO: No needs of 2 rounds. # fmt:off self.kandbox_env.kp_data_adapter.reload_data_from_db() for job in job_list: # self.kandbox_env.jobs_dict[job["job_code"]] .planning_status = job["planning_status"] # self.kandbox_env.jobs_dict[job["job_code"]] .scheduled_worker_codes = job ["scheduled_primary_worker_id"] + job ["scheduled_secondary_worker_ids"] # self.kandbox_env.jobs_dict[job["job_code"]] .scheduled_start_minutes = job["scheduled_start_minutes"] # self.kandbox_env.jobs_dict[job["job_code"]].is_changed = True # Duration does not change # self.kandbox_env.jobs_dict[job.job_code] .scheduled_start_minutes = job.["scheduled_start_minutes"] \ # + 24*60* date_util.days_between_2_day_string(start_day=self.kandbox_env.config["data_start_day"], end_day=job["scheduled_start_day"]) one_job_action_dict = ActionDict( is_forced_action=False, job_code=job["job_code"], # I assume that only in-planning jobs can appear here... action_type=ActionType.FLOATING, scheduled_worker_codes=[ job["scheduled_primary_worker_id"] ], scheduled_start_minutes=job["scheduled_start_minutes"], scheduled_duration_minutes=job[ "scheduled_duration_minutes"], ) internal_result_info = self.kandbox_env.mutate_update_job_by_action_dict( a_dict=one_job_action_dict, post_changes_flag=False) if internal_result_info.status_code != ActionScoringResultType.OK: print( f"{one_job_action_dict.job_code}: Failed to commit change, error: {str(internal_result_info)} " ) # job_to_create = copy.deepcopy(self.kandbox_env.kp_data_adapter.jobs_db_dict[job["job_code"]]) job_to_update = JobPlanningInfoUpdate( code=job["job_code"], planning_status=job["planning_status"], scheduled_start_datetime=self.kandbox_env. env_decode_from_minutes_to_datetime( job["scheduled_start_minutes"]), scheduled_duration_minutes=job[ "scheduled_duration_minutes"], scheduled_primary_worker_code=job[ "scheduled_primary_worker_id"], ) job_service.update_planning_info( db_session=self.kandbox_env.kp_data_adapter.db_session, job_in=job_to_update) # self.kandbox_env.kp_data_adapter.db_session.add(db_job) # self.kandbox_env.kp_data_adapter.db_session.commit() # job_to_create= copy.deepcopy(job_orig) print( f"job({job_to_update.code}) is updated with new planning info" ) # fmt:on # self.kandbox_env.commit_changed_jobs() current_date = current_date + timedelta(days=1)
def dispatch_jobs(self, env, rl_agent=None): if rl_agent is None: raise ValueError("rl agent can not be none!") self.kandbox_env = env rl_agent.config["nbr_of_actions"] = 2 pprint(env.get_planner_score_stats()) log.info( f"Starting Batch planning for env={self.kandbox_env.env_inst_code}, ..." ) for job_code in env.jobs_dict.keys(): if (env.jobs_dict[job_code].job_type == JobType.JOB) & (env.jobs_dict[job_code].planning_status == JobPlanningStatus.UNPLANNED): res = rl_agent.predict_action_dict_list(job_code=job_code) if len(res) < 1: log.warn(f"Failed to predict for job_code = {job_code}") continue one_job_action_dict = ActionDict( is_forced_action=True, job_code=job_code, # I assume that only in-planning jobs can appear here... action_type=ActionType.FLOATING, scheduled_worker_codes=res[0].scheduled_worker_codes, scheduled_start_minutes=res[0].scheduled_start_minutes, scheduled_duration_minutes=res[0]. scheduled_duration_minutes, ) internal_result_info = env.mutate_update_job_by_action_dict( a_dict=one_job_action_dict, post_changes_flag=True) if internal_result_info.status_code != ActionScoringResultType.OK: log.warn( # {internal_result_info} f"JOB:{ job_code}: Failed to act on job={job_code}. ") else: log.info( f"JOB:{job_code}: Successfully Planned job, action={res[0]}. " ) log.info("Batch planning Done, printing new scores...") pprint(env.get_planner_score_stats()) return for job in job_list: one_job_action_dict = ActionDict( is_forced_action=False, job_code=job["job_code"], # I assume that only in-planning jobs can appear here... action_type=ActionType.FLOATING, scheduled_worker_codes=[job["scheduled_primary_worker_id"]], scheduled_start_minutes=job["scheduled_start_minutes"], scheduled_duration_minutes=job["scheduled_duration_minutes"], ) internal_result_info = self.kandbox_env.mutate_update_job_by_action_dict( a_dict=one_job_action_dict, post_changes_flag=False) if internal_result_info.status_code != ActionScoringResultType.OK: print( f"{one_job_action_dict.job_code}: Failed to commit change, error: {str(internal_result_info)} " ) job_to_update = JobPlanningInfoUpdate( code=job["job_code"], planning_status=job["planning_status"], scheduled_start_datetime=self.kandbox_env. env_decode_from_minutes_to_datetime( job["scheduled_start_minutes"]), scheduled_duration_minutes=job["scheduled_duration_minutes"], scheduled_primary_worker_code=job[ "scheduled_primary_worker_id"], ) job_service.update_planning_info( db_session=self.kandbox_env.kp_data_adapter.db_session, job_in=job_to_update) print( f"job({job_to_update.code}) is updated with new planning info")
def dispatch_jobs(self, env, rl_agent=None): # rl_agent will not be used. # , start_date="20191101", end_date="20191230" self.kandbox_env = env GENERATOR_START_DATE = datetime.strptime( self.kandbox_env.config["env_start_day"], config.KANDBOX_DATE_FORMAT) GENERATOR_END_DATE = GENERATOR_START_DATE + timedelta( days=self.kandbox_env.config["nbr_of_days_planning_window"]) current_date = GENERATOR_START_DATE print("current_start_day", current_date, "GENERATOR_END_DATE", GENERATOR_END_DATE) slot_keys = list(self.kandbox_env.slot_server.time_slot_dict.keys()) worker_slots = [ self.kandbox_env.slot_server.time_slot_dict[key] for key in slot_keys ] # TODO, do it per day jobs_orig = [] for j in self.kandbox_env.jobs: if j.planning_status in (JobPlanningStatus.UNPLANNED, JobPlanningStatus.IN_PLANNING): jobs_orig.append(j) if len(jobs_orig) < 1: print("it is empty, nothing to dispatch!") return current_shifts = jobs_orig print({ "loaded day": GENERATOR_START_DATE, "job count": len(current_shifts), "worker_slots count": len(worker_slots), }) worker_day = self.dispatch_jobs_to_slots( jobs=current_shifts, worker_slots=worker_slots) # [0:20] [:70] if len(worker_day) < 1: print("no data returned from dispataching!") return # pprint(worker_day) job_list = [] for w_i in range(len(worker_day)): # worker_day is the dispatched result, now we save it into DB. pre_job_code = "__HOME" for task in worker_day[w_i][:-1]: task_id = task[0] # + 1 worker_code = worker_slots[w_i].worker_id # updated_order = {} # latest_order_dict[id] job_list.append({ "job_code": current_shifts[task_id].job_code, "job_schedule_type": current_shifts[task_id].job_schedule_type, "planning_status": JobPlanningStatus.IN_PLANNING, "scheduled_primary_worker_id": worker_code, "scheduled_start_minutes": task[1] + worker_slots[w_i].start_minutes, "scheduled_duration_minutes": current_shifts[task_id].requested_duration_minutes, "scheduled_travel_minutes_before": task[2], "scheduled_travel_prev_code": pre_job_code, "geo_longitude": current_shifts[task_id].location[0], "geo_latitude": current_shifts[task_id].location[1], "conflict_level": 0, "scheduled_secondary_worker_ids": "[]", "scheduled_share_status": "N", "error_message": "", }) pre_job_code = current_shifts[task_id].job_code """ import pprint pprint.pprint(job_list) return """ # TODO: No needs of 2 rounds. # fmt:off self.kandbox_env.kp_data_adapter.reload_data_from_db() for job in job_list: one_job_action_dict = ActionDict( is_forced_action=False, job_code=job["job_code"], # I assume that only in-planning jobs can appear here... action_type=ActionType.FLOATING, scheduled_worker_codes=[job["scheduled_primary_worker_id"]], scheduled_start_minutes=job["scheduled_start_minutes"], scheduled_duration_minutes=job["scheduled_duration_minutes"], ) internal_result_info = self.kandbox_env.mutate_update_job_by_action_dict( a_dict=one_job_action_dict, post_changes_flag=False) if internal_result_info.status_code != ActionScoringResultType.OK: print( f"{one_job_action_dict.job_code}: Failed to commit change, error: {str(internal_result_info)} " ) job_to_update = JobPlanningInfoUpdate( code=job["job_code"], job_type=JobType.JOB, planning_status=job["planning_status"], scheduled_start_datetime=self.kandbox_env. env_decode_from_minutes_to_datetime( job["scheduled_start_minutes"]), scheduled_duration_minutes=job["scheduled_duration_minutes"], scheduled_primary_worker_code=job[ "scheduled_primary_worker_id"], ) job_service.update_planning_info( db_session=self.kandbox_env.kp_data_adapter.db_session, job_in=job_to_update) print( f"job({job_to_update.code}) is updated with new planning info")
def dispatch_jobs(self, env): # , start_date="20191101", end_date="20191230" assert env.config["nbr_of_days_planning_window"] == 1 if len(env.jobs) < 1: print("it is empty, nothing to dispatch!") return env.workers = env.workers # [0:50] env.jobs = env.jobs # [0:500] self.env = env begin_time = datetime.now() avg_long = sum([j.location.geo_longitude for j in self.env.jobs]) / len(self.env.jobs) avg_lat = sum([j.location.geo_latitude for j in self.env.jobs]) / len(self.env.jobs) AVG_JOB_LOCATION = JobLocationBase( geo_longitude=avg_long, geo_latitude=avg_lat, location_type=LocationType.HOME, location_code="depot", ) self.job_locations = [AVG_JOB_LOCATION] for j in self.env.jobs: self.job_locations.append(j.location) # Create and register a transit callback. def distance_callback(from_index, to_index): # Convert from routing variable Index to distance matrix NodeIndex. from_node = manager.IndexToNode(from_index) # - 1 to_node = manager.IndexToNode(to_index) # - 1 if from_node == to_node: return 0 return self._get_travel_time_2locations( self.job_locations[from_node], self.job_locations[to_node]) # Create the routing index manager. manager = pywrapcp.RoutingIndexManager(len(self.job_locations), len(self.env.workers), 0) # Create Routing Model. routing = pywrapcp.RoutingModel(manager) transit_callback_index = routing.RegisterTransitCallback( distance_callback) # Define cost of each arc. routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index) # Add Distance constraint. dimension_name = "Distance" routing.AddDimension( transit_callback_index, 0, # no slack 300, # vehicle maximum travel distance True, # start cumul to zero dimension_name, ) distance_dimension = routing.GetDimensionOrDie(dimension_name) distance_dimension.SetGlobalSpanCostCoefficient(100) # Setting first solution heuristic. search_parameters = pywrapcp.DefaultRoutingSearchParameters() search_parameters.first_solution_strategy = ( routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC) # Solve the problem. solution = routing.SolveWithParameters(search_parameters) # Print solution on console. if solution: print_solution(self, manager, routing, solution) total_time = datetime.now() - begin_time # Date: {begin_time}, print( f"nbr workers: {len(self.env.workers)}, nbr jobs: {len(self.job_locations)}, Elapsed: {total_time}" ) return while current_date < GENERATOR_END_DATE: print("current_start_day", current_date, "GENERATOR_END_DATE", GENERATOR_END_DATE) current_start_day = datetime.strftime(current_date, config.KANDBOX_DATE_FORMAT) day_seq = int( self.env.env_encode_from_datetime_to_minutes(current_date) / 1440) # purge job status for this planner and this day. workers = self.env.workers # start_day=current_start_day # TODO, do it per day jobs_orig = [] for j in self.env.jobs: if (j.requested_start_minutes >= day_seq * 1440) and (j.requested_start_minutes < (day_seq + 1) * 1440): jobs_orig.append(j) if len(jobs_orig) < 1: print("it is empty, nothing to dispatch!") return current_shifts = jobs_orig current_workers = workers # .T.to_dict().values() print({ "loaded day": current_date, "job count": len(current_shifts), "current_workers count": len(current_workers), }) worker_day = self.dispatch_jobs_1day( jobs=current_shifts[0:53], workers=current_workers) # [0:20] [:70] # worker_day is the dispatched result, now we save it into DB. if len(worker_day) < 1: print( "no data returned from opti1day! I will move on to next day!" ) current_date = current_date + timedelta(days=1) continue # pprint(worker_day) job_list = [] for w_i in range(len(worker_day)): pre_job_code = "__HOME" for task in worker_day[w_i][:-1]: task_id = task[0] # + 1 worker_code = current_workers[w_i].worker_code # updated_order = {} # latest_order_dict[id] job_list.append({ "job_code": current_shifts[task_id].job_code, "job_schedule_type": current_shifts[task_id].job_schedule_type, "planning_status": JobPlanningStatus.IN_PLANNING, "scheduled_primary_worker_id": worker_code, "scheduled_start_day": datetime.strftime(current_date, config.KANDBOX_DATE_FORMAT), "requested_start_day": datetime.strftime(current_date, config.KANDBOX_DATE_FORMAT), "scheduled_start_minutes": task[1] + (day_seq * 1440), "scheduled_duration_minutes": current_shifts[task_id].requested_duration_minutes, "scheduled_travel_minutes_before": task[2], "scheduled_travel_prev_code": pre_job_code, # "location_code": current_shifts[task_id]["location_code"], "geo_longitude": current_shifts[task_id].location[0], "geo_latitude": current_shifts[task_id].location[1], "conflict_level": 0, "scheduled_secondary_worker_ids": "[]", "scheduled_share_status": "N", "error_message": "", }) pre_job_code = current_shifts[task_id].job_code """ import pprint pprint.pprint(job_list) return """ # TODO: No needs of 2 rounds. # fmt:off self.env.kp_data_adapter.reload_data_from_db() for job in job_list: one_job_action_dict = ActionDict( is_forced_action=False, job_code=job["job_code"], action_type=ActionType. FLOATING, # I assume that only in-planning jobs can appear here... scheduled_worker_codes=[ job["scheduled_primary_worker_id"] ], scheduled_start_minutes=job["scheduled_start_minutes"], scheduled_duration_minutes=job[ "scheduled_duration_minutes"], ) internal_result_info = self.env.mutate_update_job_by_action_dict( a_dict=one_job_action_dict, post_changes_flag=False) if internal_result_info.status_code != ActionScoringResultType.OK: print( f"{one_job_action_dict.job_code}: Failed to commit change, error: {str(internal_result_info)} " ) # job_to_create = copy.deepcopy(self.env.kp_data_adapter.jobs_db_dict[job["job_code"]]) job_to_update = JobPlanningInfoUpdate( code=job["job_code"], planning_status=job["planning_status"], scheduled_start_datetime=self.env. env_decode_from_minutes_to_datetime( job["scheduled_start_minutes"]), scheduled_duration_minutes=job[ "scheduled_duration_minutes"], scheduled_primary_worker_code=job[ "scheduled_primary_worker_id"], ) job_service.update_planning_info( db_session=self.env.kp_data_adapter.db_session, job_in=job_to_update) # self.env.kp_data_adapter.db_session.add(db_job) # self.env.kp_data_adapter.db_session.commit() # job_to_create= copy.deepcopy(job_orig) print( f"job({job_to_update.code}) is updated with new planning info" ) # fmt:on current_date = current_date + timedelta(days=1)