[Python] How to implement Timefold constraint with helper function? print() unexpected behavior

Discussão em 'Python' iniciado por Stack, Fevereiro 24, 2025.

  1. Stack

    Stack Membro Participativo

    The problem: Scheduling workers for two-hour shifts. The objective is to maximize the number of shifts each worker completes within an 8-hour working period. The working period for each employee starts with their first shift and must be followed by a mandatory 10-hour rest period. Additionally, there must be no overlapping shifts assigned to the same worker.

    I have based my implementation on the employee_scheduling quickstart project with Python. The domain and core constraints are the same, including:

    required_skill(constraint_factory)
    no_overlapping_shifts(constraint_factory)
    unavailable_employee(constraint_factory)
    undesired_day_for_employee(constraint_factory)
    desired_day_for_employee(constraint_factory)


    Additionally, I implemented a custom constraint:

    enforce_10_hour_rest_period(constraint_factory)


    The constraint code:

    def enforce_10_hour_rest_period(constraint_factory):

    return (
    constraint_factory.for_each(Shift)
    .filter(lambda shift: shift.employee is not None)
    .group_by(
    lambda shift: shift.employee, ConstraintCollectors.to_list())
    .map(lambda employee_name, shifts: calculate_insufficient_breaks(employee_name, sorted(shifts, key=lambda sh: sh.start)))
    .penalize(
    HardSoftDecimalScore.ONE_HARD,
    lambda insufficient_breaks: insufficient_breaks
    )
    .as_constraint("10-hour rest period after 8-hour working period")
    )


    The helper function code:

    def rest_period_sufficient(last_shift_end, next_shift_start):
    return (next_shift_start - last_shift_end).total_seconds() >= 10 * 3600



    def calculate_insufficient_breaks(employee, shifts):
    filtered_shifts = [shift for shift in shifts if shift.employee.name == employee.name]
    l = len(filtered_shifts)
    if len(filtered_shifts) < 2:
    return 0

    insufficient_breaks = 0
    working_period_end = filtered_shifts[0].start + timedelta(hours=8)

    #delete print line
    print(f"working period start: {filtered_shifts[0].start}, working period end {working_period_end}")

    for i in range(1, l):
    #delete print line
    print(f"start: {filtered_shifts.start}, end: {filtered_shifts.end} ")

    if filtered_shifts.end >= working_period_end:
    if not rest_period_sufficient(working_period_end, filtered_shifts.start):
    insufficient_breaks += 1
    working_period_end = filtered_shifts.start + timedelta(hours=8)
    return insufficient_breaks


    1 Question: Issue: In the calculate_insufficient_breaks function if I print shift.employee.name values I would receive different employee names. While filtering seems necessary, it is unclear why this occurs given the groupby used in the enforce_10_hour_rest_period function. Furthermore, I observed that removing the first print line - The algorithm (constraint) does not work, the gaps are not 10 hours. And if I remove the second print statement I get the following exception: typeerror: rest_period_sufficient() got an unexpected keyword argument 'hours'.

    The algorithm runs as expected when the print statement remains, but fails otherwise. Why this occurs? Is my current implementation appropriate, or is there any a more efficient approach to defining this constraint?

    1. Question: Maximizing number of Shifts Within 8-Hour Windows:

    I aim to maximize the number of shifts a worker completes within their 8-hour work period. My plan was to create a helper function similar to the 10-hour rest period constraint and apply a reward when the maximum number of shifts is achieved within the 8-hour window. Is there an optimal way to construct this constraint in Timefold?

    Continue reading...

Compartilhe esta Página