The operations manager of QuickShip Logistics is trying to determine the number of warehouse supervisors needed to manage the company's distribution operations throughout the year. The estimated number of operational hours required each month is as follows:  

| Month     | Operational Hours Needed |  
|-----------|--------------------------|  
| January   | 600                      |  
| February  | 450                      |  
| March     | 350                      |  
| April     | 800                      |  
| May       | 1,000                    |  
| June      | 450                      |  
| July      | 350                      |  
| August    | 600                      |  
| September | 450                      |  
| October   | 350                      |  
| November  | 250                      |  
| December  | 150                      |  

QuickShip Logistics aims to determine the optimal number of full-time warehouse supervisors to employ for the entire year, with the primary goal of minimizing total annual labor costs. These costs are composed of fixed monthly salaries of $5,000 for each supervisor (who are employed for all 12 months) and any overtime expenses incurred to meet fluctuating monthly operational hour demands. The company must ensure all operational tasks for each month are completed within that month, and all annual tasks are finalized by the end of December. 

Each of the N supervisors, when not taking their mandatory one-month annual paid vacation, can provide up to 160 standard operational hours monthly. To cover demands exceeding this standard capacity, these non-vacationing supervisors can work up to an additional 40 hours of overtime each per month, with overtime labor paid at a rate of $45 per hour. How can QuickShip Logistics minimize employment costs?

Problem type: ILP (Medium: 25 variables, 37 constraints, 85 nonzeros)

Mathematical model:
Set:
1. Month
The set of months, \( M = \{1, 2, \ldots, 12\} \)  # 1: January, 2: February, ..., 12: December

Parameter:
1. OperationalHoursNeeded
[600, 450, 350, 800, 1000, 450, 350, 600, 450, 350, 250, 150]  # operational hours needed for each month
2. NormalHoursPerSupervisorPerMonth
160  # hours each supervisor can handle per month
3. OvertimeHoursPerSupervisorPerMonth
40  # additional hours each supervisor can work per month as overtime
4. SupervisorsVacationMonths
1
5. FixedSalaryPerSupervisorPerMonth
5000  # fixed monthly salary for each supervisor
6. OvertimeRatePerHour
45  # overtime pay rate per hour

Decision variable:
1. SupervisorsNeeded
Integer variable, \( SupervisorsNeeded \), the number of warehouse supervisors needed.
2. WorkSupervisorPerMonth
Integer variable, \( WorkSupervisorPerMonth[m] \forall m \in M \), the number of hours worked by supervisors in month \( m \).
3. OvertimeHoursPerMonth
Integer variable, \( OvertimeHoursPerMonth[m] \forall m \in M \), the total overtime hours worked by supervisors in month \( m \).

Objective:
1. Minimize the total cost.
min: FixedSalaryPerSupervisorPerMonth \times SupervisorsNeeded \times 12 + \sum_{m \in M} (OvertimeRatePerHour \times OvertimeHoursPerMonth[m])

Constraint:
1. Operational hours constraint.
WorkSupervisorPerMonth[m] \times NormalHoursPerSupervisorPerMonth + OvertimeHoursPerMonth[m] \geq OperationalHoursNeeded[m] \forall m \in M
2. Vacation constraint.
\sum_{m \in M} WorkSupervisorPerMonth[m] = (12 - SupervisorsVacationMonths) \times SupervisorsNeeded
3. Supervisors number constraint.
WorkSupervisorPerMonth[m] \leq SupervisorsNeeded \forall m \in M
4. Overtime hours constraint.
OvertimeHoursPerMonth[m] \leq WorkSupervisorPerMonth[m] \times OvertimeHoursPerSupervisorPerMonth \forall m \in M
5. Non-negativity constraint.
SupervisorsNeeded \geq 0
WorkSupervisorPerMonth[m] \geq 0 \forall m \in M

Type:
Integer, Linear, Linear
ILP

Gurobi implementation:
import gurobipy as gp
from gurobipy import GRB


def solve_supervisor_scheduling(
    operational_hours_needed={
        1: 600, 2: 450, 3: 350, 4: 800, 5: 1000, 6: 450,
        7: 350, 8: 600, 9: 450, 10: 350, 11: 250, 12: 150
    },
    normal_hours_per_supervisor=160,
    overtime_hours_per_supervisor=40,
    supervisors_vacation_months=1,
    fixed_salary=5000,
    overtime_rate=45
):
    """
    Models and solves the supervisor scheduling problem.
    """
    model = gp.Model("WarehouseSupervisors")
    model.setParam('OutputFlag', 0)

    # --- Parameters ---
    months = range(1, 13)

    # --- Decision Variables ---
    supervisors_needed = model.addVar(vtype=GRB.INTEGER, name="SupervisorsNeeded")
    work_supervisor_per_month = model.addVars(months, vtype=GRB.INTEGER, name="WorkSupervisorPerMonth")
    overtime_hours_per_month = model.addVars(months, vtype=GRB.INTEGER, name="OvertimeHoursPerMonth")

    # --- Objective Function ---
    total_cost = fixed_salary * supervisors_needed * 12
    for m in months:
        total_cost += overtime_rate * overtime_hours_per_month[m]
    model.setObjective(total_cost, GRB.MINIMIZE)

    # --- Constraints ---
    for m in months:
        model.addConstr(
            work_supervisor_per_month[m] * normal_hours_per_supervisor +
            overtime_hours_per_month[m] >= operational_hours_needed[m],
            name=f"OperationalHoursNeeded_{m}"
        )

    model.addConstr(
        gp.quicksum(work_supervisor_per_month[m] for m in months) ==
        (12 - supervisors_vacation_months) * supervisors_needed,
        name="VacationConstraint"
    )

    for m in months:
        model.addConstr(
            work_supervisor_per_month[m] <= supervisors_needed,
            name=f"SupervisorsNumberConstraint_{m}"
        )

    for m in months:
        model.addConstr(
            overtime_hours_per_month[m] <= work_supervisor_per_month[m] * overtime_hours_per_supervisor,
            name=f"OvertimeLimit_{m}"
        )

    # --- Solve ---
    model.optimize()

    # --- Return Results ---
    if model.status == GRB.OPTIMAL:
        return {"status": "optimal", "obj": model.ObjVal}
    else:
        return {"status": f"{model.status}"}


if __name__ == '__main__':
    result = solve_supervisor_scheduling()
    print(result)

Solution status: OPTIMAL
Optimal objective value: 309000