Esempio n. 1
0
    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)
Esempio n. 2
0
    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)
Esempio n. 3
0
    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")
Esempio n. 5
0
    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)