Skip to main content
Glama
nurse_scheduling.py9.9 kB
""" Nurse Scheduling Optimization Example This module demonstrates staff scheduling optimization for a hospital nursing department. The problem involves assigning nurses to shifts across multiple days while satisfying operational requirements and work-life balance constraints. The constraint programming problem assigns nurses to shifts subject to: - Each shift must be assigned to exactly one nurse each day - Each nurse works at most one shift per day - Workload distribution: each nurse works 2-3 shifts over the period - Special constraints (e.g., certain nurses unavailable on specific days) - Minimum staffing requirements per shift type This is a classic constraint satisfaction problem suitable for OR-Tools CP-SAT. """ from returns.result import Success from usolver_mcp.models.ortools_models import ( Constraint, Objective, ObjectiveType, Problem, Variable, VariableType, ) from usolver_mcp.solvers.ortools_solver import solve_problem def create_nurse_scheduling_problem(): """ Create a nurse scheduling optimization problem. Returns: Problem: The OR-Tools constraint programming problem """ # Data nurses = ["Alice", "Bob", "Charlie", "Diana"] shifts = ["Morning (7AM-3PM)", "Evening (3PM-11PM)", "Night (11PM-7AM)"] days = ["Monday", "Tuesday", "Wednesday"] num_nurses = len(nurses) num_shifts = len(shifts) num_days = len(days) # Create decision variables # shifts_var[n,d,s]: nurse 'n' works shift 's' on day 'd' shifts_var = Variable( name="shifts_var", type=VariableType.BOOLEAN, shape=[num_nurses, num_days, num_shifts], description="Binary variable indicating if a nurse works a particular shift on a particular day", ) # Create constraints constraints = [] # Constraint 1: Each shift must be assigned to exactly one nurse each day for d in range(num_days): for s in range(num_shifts): constraints.append( Constraint( expression=f"model.add(sum([shifts_var[n][{d}][{s}] for n in range({num_nurses})]) == 1)", description=f"Exactly one nurse must work the {shifts[s]} shift on {days[d]}", ) ) # Constraint 2: Each nurse works at most one shift per day for n in range(num_nurses): for d in range(num_days): constraints.append( Constraint( expression=f"model.add(sum([shifts_var[{n}][{d}][s] for s in range({num_shifts})]) <= 1)", description=f"{nurses[n]} works at most one shift on {days[d]}", ) ) # Constraint 3: Distribute shifts evenly (2-3 shifts per nurse) min_shifts_per_nurse = (num_shifts * num_days) // num_nurses max_shifts_per_nurse = min_shifts_per_nurse + ( 1 if num_shifts * num_days % num_nurses else 0 ) for n in range(num_nurses): # Each nurse must work at least min_shifts_per_nurse shifts constraints.append( Constraint( expression=( f"model.add(sum([shifts_var[{n}][d][s] " f"for d in range({num_days}) " f"for s in range({num_shifts})]) >= {min_shifts_per_nurse})" ), description=f"{nurses[n]} must work at least {min_shifts_per_nurse} shifts", ) ) # Each nurse must work at most max_shifts_per_nurse shifts constraints.append( Constraint( expression=( f"model.add(sum([shifts_var[{n}][d][s] " f"for d in range({num_days}) " f"for s in range({num_shifts})]) <= {max_shifts_per_nurse})" ), description=f"{nurses[n]} must work at most {max_shifts_per_nurse} shifts", ) ) # Constraint 4: Charlie can't work on Tuesday (example of special constraint) charlie_index = nurses.index("Charlie") tuesday_index = days.index("Tuesday") for s in range(num_shifts): constraints.append( Constraint( expression=f"model.add(shifts_var[{charlie_index}][{tuesday_index}][{s}] == 0)", description="Charlie cannot work any shift on Tuesday", ) ) # Optional: Add an objective to minimize schedule disruption # (e.g., minimize night shifts followed by morning shifts) objective = Objective( type=ObjectiveType.FEASIBILITY, description="Find any feasible schedule" ) return Problem( variables=[shifts_var], constraints=constraints, objective=objective, description="Hospital nurse scheduling problem with fairness and availability constraints", parameters={"enumerate_all_solutions": False}, ) def solve_nurse_scheduling(): """ Solve the nurse scheduling problem and return results. Returns: dict: Solution results including schedule and statistics """ problem = create_nurse_scheduling_problem() result = solve_problem(problem) nurses = ["Alice", "Bob", "Charlie", "Diana"] shifts = ["Morning (7AM-3PM)", "Evening (3PM-11PM)", "Night (11PM-7AM)"] days = ["Monday", "Tuesday", "Wednesday"] match result: case Success(solution): if solution.is_feasible: schedule_val = solution.values["shifts_var"] # Create readable schedule schedule = {} for d, day in enumerate(days): schedule[day] = {} for s, shift in enumerate(shifts): for n, nurse in enumerate(nurses): if schedule_val[n][d][s]: schedule[day][shift] = nurse break # Calculate nurse workloads nurse_shifts = {} for n, nurse in enumerate(nurses): total_shifts = sum( schedule_val[n][d][s] for d in range(len(days)) for s in range(len(shifts)) ) nurse_shifts[nurse] = total_shifts return { "status": "feasible", "schedule": schedule, "nurse_shifts": nurse_shifts, "statistics": solution.statistics, } else: return { "status": solution.status, "error": "No feasible schedule found", } case _: return {"status": "error", "error": "Failed to solve problem"} def print_results(results) -> None: """Print nurse scheduling results in a formatted way.""" if results["status"] != "feasible": print(f"Problem Status: {results['status']}") if "error" in results: print(f"Error: {results['error']}") return print("Nurse Scheduling Optimization Results") print("=" * 60) # Print weekly schedule schedule = results["schedule"] print("\nWeekly Schedule:") print("-" * 60) for day, shifts_for_day in schedule.items(): print(f"\n{day}:") for shift, nurse in shifts_for_day.items(): print(f" {shift}: {nurse}") # Print workload distribution nurse_shifts = results["nurse_shifts"] print("\nWorkload Distribution:") print("-" * 30) for nurse, shifts in nurse_shifts.items(): print(f"{nurse}: {shifts} shifts") # Print solver statistics if "statistics" in results: print("\nSolver Statistics:") print("-" * 30) for key, value in results["statistics"].items(): display_key = " ".join(word.title() for word in key.split("_")) print(f"{display_key}: {value}") def validate_schedule(results) -> bool: """Validate that the schedule meets all constraints.""" if results["status"] != "feasible": return False schedule = results["schedule"] # Check that each shift is assigned for _day, shifts_for_day in schedule.items(): if len(shifts_for_day) != 3: # Should have 3 shifts per day return False # Check Charlie doesn't work on Tuesday if "Charlie" in schedule.get("Tuesday", {}).values(): return False # Check workload distribution (2-3 shifts per nurse) nurse_shifts = results["nurse_shifts"] for _nurse, shifts in nurse_shifts.items(): if not (2 <= shifts <= 3): return False return True def main() -> None: """Main function to run the nurse scheduling example.""" print(__doc__) results = solve_nurse_scheduling() print_results(results) # Validate the solution if validate_schedule(results): print("\n✓ Schedule validation passed!") else: print("\n✗ Schedule validation failed!") def test_nurse_scheduling() -> None: """Test function for pytest.""" results = solve_nurse_scheduling() # Test that we get a feasible solution assert results["status"] == "feasible" # Test that we have a complete schedule schedule = results["schedule"] assert len(schedule) == 3 # 3 days for day_schedule in schedule.values(): assert len(day_schedule) == 3 # 3 shifts per day # Test workload distribution nurse_shifts = results["nurse_shifts"] for _nurse, shifts in nurse_shifts.items(): assert 2 <= shifts <= 3 # Each nurse works 2-3 shifts # Test special constraint: Charlie doesn't work on Tuesday assert "Charlie" not in schedule.get("Tuesday", {}).values() # Test that schedule is valid assert validate_schedule(results) if __name__ == "__main__": main()

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/sdiehl/usolver'

If you have feedback or need assistance with the MCP directory API, please join our Discord server