Beispiel #1
0
def completion_time(request):
    start_time = request.GET.get("start", "")
    hours = request.GET.get("hours", "")
    try:
        start_time = datetime.fromisoformat(start_time)
    except ValueError:
        return JsonResponse(
            {"error": "Invalid start time format. Expected ISO format.", "resp": None}
        )

    try:
        hours = int(hours)
    except ValueError:
        return JsonResponse(
            {"error": "Invalid number of hours. Expected an integer.", "resp": None}
        )
    end_time = utils.compute_end_time(start_time, hours)
    return JsonResponse({"error": None, "resp": end_time.isoformat()})
Beispiel #2
0
 def completion_time(self):
     if self.override_hours:
         return self.start_time + timedelta(
             hours=float(self.override_hours))
     return utils.compute_end_time(self.start_time, self.hours)
Beispiel #3
0
def schedule(
    items: List[Item],
    existing_items: Mapping[str, List[ExistingItem]],
    start: datetime,
    end: datetime,
) -> List[Mapping[str, str]]:
    """
    Schedules the items according the following algorithm:
    :param items: a list of Item objects to be scheduled
    :param existing_items: a mapping from group to ExistingItem objects that are
        already on the timeline and therefore cannot be changed.
    :param start: the earliest time when the items can be scheduled to start
    :param end: the latest time when the items can be scheduled to finish
    :return: a list of schedules
    """
    schedules: List[Schedule] = []
    logger.info(
        "Scheduling %d items from %s to %s",
        len(items),
        start.isoformat(),
        end.isoformat(),
    )
    # Prioritize item with earliest deadline, with a tiebreaker of shortest
    # time first
    items.sort(key=lambda item: (item.item.goal.deadline, item.hours))
    ml_schedules = _get_available_blocks(existing_items, start, end)

    for item in items:
        min_earliest_time = end
        min_ml = None
        min_block_id = None
        for ml in item.groups:
            earliest_time = end
            earliest_block_id = None
            for i, (st, et) in enumerate(ml_schedules[ml]):
                if compute_end_time(st, item.hours) <= et:
                    earliest_time = st
                    earliest_block_id = i
                    break
            if earliest_time < min_earliest_time:
                min_earliest_time = earliest_time
                min_ml = ml
                min_block_id = earliest_block_id
        if min_ml is None:
            raise ScheduleException("Unable to schedule: too many items.")
        else:
            item_end_time = compute_end_time(min_earliest_time, item.hours)
            if timezone.make_naive(item_end_time) > datetime.combine(
                item.item.goal.deadline, WORK_HOURS_END
            ):
                raise ScheduleException("Unable to schedule: would exceed deadline.")
            schedules.append(
                Schedule(item.item.id, min_earliest_time, item_end_time, min_ml)
            )
            st, et = ml_schedules[min_ml][min_block_id]
            if et == item_end_time:
                del ml_schedules[min_ml][min_block_id]
            else:
                ml_schedules[min_ml][min_block_id] = (item_end_time, et)
    logger.info("Scheduled %d items: %s", len(schedules), schedules)
    return list(map(Schedule.to_dict, schedules))
Beispiel #4
0
def schedule_max_profit(
    items: List[Item],
    existing_items: Mapping[str, List[ExistingItem]],
    start: datetime,
    end: datetime,
) -> List[Mapping[str, str]]:
    """
    Schedules the items according the following algorithm:
    :param items: a list of Item objects to be scheduled
    :param existing_items: a mapping from group to ExistingItem objects that are
        already on the timeline and therefore cannot be changed.
    :param start: the earliest time when the items can be scheduled to start
    :param end: the latest time when the items can be scheduled to finish
    :return: a list of schedules
    """
    schedules: List[Schedule] = []
    logger.info(
        "Scheduling %d items from %s to %s",
        len(items),
        start.isoformat(),
        end.isoformat(),
    )
    # Prioritize item with earliest deadline, with a tiebreaker of shortest
    # time first
    items.sort(
        key=lambda item: (
            item.item.sku.sales.aggregate(revenue=Sum(F("price") * F("sales")))[
                "revenue"
            ],
            item.hours,
        )
    )
    ml_schedules = _get_available_blocks(existing_items, start, end)

    for item in items:
        max_latest_time = start
        opt_ml = None
        opt_block_id = None
        item_deadline = start.tzinfo.localize(
            datetime.combine(item.item.goal.deadline, WORK_HOURS_END)
        )
        for ml in item.groups:
            latest_time = end
            latest_block_id = None
            found = False
            for i, (st, et) in reversed(list(enumerate(ml_schedules[ml]))):
                deadline = et if et <= item_deadline else item_deadline
                if compute_start_time(deadline, item.hours) >= st:
                    latest_time = compute_start_time(deadline, item.hours)
                    latest_block_id = i
                    found = True
                    break
            if found:
                max_latest_time = latest_time
                opt_ml = ml
                opt_block_id = latest_block_id
        if opt_ml is None:
            raise ScheduleException("Unable to schedule: too many items.")
        else:
            item_end_time = compute_end_time(max_latest_time, item.hours)
            schedules.append(
                Schedule(item.item.id, max_latest_time, item_end_time, opt_ml)
            )
            st, et = ml_schedules[opt_ml][opt_block_id]
            if st == max_latest_time:
                del ml_schedules[opt_ml][opt_block_id]
            else:
                ml_schedules[opt_ml][opt_block_id] = (st, max_latest_time)
    logger.info("Scheduled %d items: %s", len(schedules), schedules)
    return list(map(Schedule.to_dict, schedules))
Beispiel #5
0
 def test_compute_end_time(self):
     tz = timezone.get_current_timezone()
     test_cases = [
         (
             (tz.localize(datetime(2019, 2, 21, 9, 0, 0)), 1),
             tz.localize(datetime(2019, 2, 21, 10, 0, 0)),
             "9AM, 1 hour, 10AM",
         ),
         (
             (tz.localize(datetime(2019, 2, 21, 9, 0, 0)), 10),
             tz.localize(datetime(2019, 2, 22, 9, 0, 0)),
             "9AM, 10 hours, 9AM next day",
         ),
         (
             (tz.localize(datetime(2019, 2, 21, 9, 0, 0)), 24),
             tz.localize(datetime(2019, 2, 23, 13, 0, 0)),
             "9AM, 24 hours, 1PM two days later",
         ),
         (
             (tz.localize(datetime(2019, 2, 21, 20, 0, 0)), 11),
             tz.localize(datetime(2019, 2, 23, 9, 0, 0)),
             "8PM, 11 hours, 9AM two days later (no work first day)",
         ),
         (
             (tz.localize(datetime(2019, 2, 21, 18, 0, 0)), 10),
             tz.localize(datetime(2019, 2, 22, 18, 0, 0)),
             "6PM, 10 hours, 6PM next day",
         ),
         (
             (tz.localize(datetime(2019, 2, 21, 8, 0, 0)), 10),
             tz.localize(datetime(2019, 2, 21, 18, 0, 0)),
             "8AM, 10 hours, 6PM",
         ),
         (
             (tz.localize(datetime(2019, 2, 21, 8, 0, 0)), 0.5),
             tz.localize(datetime(2019, 2, 21, 8, 30, 0)),
             "8AM, 30 minutes, 8:30AM",
         ),
         (
             (tz.localize(datetime(2019, 2, 21, 8, 0, 0)), 10.5),
             tz.localize(datetime(2019, 2, 22, 8, 30, 0)),
             "8AM, 10.5 hours, 8:30AM next day",
         ),
         (
             (tz.localize(datetime(2019, 2, 21, 1, 0, 0)), 4),
             tz.localize(datetime(2019, 2, 21, 12, 0, 0)),
             "1AM, 4 hours, 12PM same day",
         ),
         ((tz.localize(datetime(2019, 3, 9, 16, 0, 0)), 4),
          tz.localize(datetime(2019, 3, 10, 10, 0, 0)),
          "4PM, 4 hours, 10 AM next day, across DST switch")
     ]
     for (start_time, hours), expected, msg in test_cases:
         print(
             "start:   ",
             start_time.strftime("%Y-%m-%d %H:%M:%S %Z%z"),
             "+",
             hours,
             "hours",
         )
         end_time = utils.compute_end_time(start_time, hours)
         print("end:     ", end_time.strftime("%Y-%m-%d %H:%M:%S %Z%z"))
         print("expected:", expected.strftime("%Y-%m-%d %H:%M:%S %Z%z"))
         self.assertEqual(end_time, expected, msg)