### Verify pyworkforce installation Source: https://pyworkforce.rodrigo-arenas.com/stable/guide/installation Imports pyworkforce and prints its version to confirm the installation. ```python import pyworkforce print(pyworkforce.__version__) ``` -------------------------------- ### Install pyworkforce with pip Source: https://pyworkforce.rodrigo-arenas.com/stable/guide/installation Installs the pyworkforce package from PyPI. Works on Linux, macOS, and Windows. ```bash pip install pyworkforce ``` -------------------------------- ### Solution dictionary example Source: https://pyworkforce.rodrigo-arenas.com/stable/guide/scheduling Shows the structure of the dictionary returned by solve() and stored as solution_. It contains status, cost, and resources_shifts keys. The cost is the final objective value, and resources_shifts lists resources per day and shift. ```python { "status": "OPTIMAL", "cost": 157.0, "resources_shifts": [ {"day": 0, "shift": "Morning", "resources": 8}, {"day": 0, "shift": "Afternoon", "resources": 11}, ... ], } ``` -------------------------------- ### Development install of pyworkforce Source: https://pyworkforce.rodrigo-arenas.com/stable/guide/installation Clones the repository, installs in editable mode with dev dependencies, and runs tests and linting. ```bash git clone https://github.com/rodrigo-arenas/pyworkforce.git cd pyworkforce pip install -e ".[dev]" pytest pyworkforce/ ruff check pyworkforce/ ``` -------------------------------- ### Example: Using MinAbsDifference and MinRequiredResources Source: https://pyworkforce.rodrigo-arenas.com/stable/guide/scheduling Shows how to instantiate both solvers with the same inputs and print the status of the solution. The shifts_coverage dictionary defines four shifts (Morning, Afternoon, Night, Mixed) with 24 periods. required_resources is a list of two days, each with 24 values. Both solvers are configured with num_days=2, periods=24, max_period_concurrency=27, and max_shift_concurrency=25. ```python from pyworkforce.scheduling import MinAbsDifference, MinRequiredResources required_resources = [ [9, 11, 17, 9, 7, 12, 5, 11, 8, 9, 18, 17, 8, 12, 16, 8, 7, 12, 11, 10, 13, 19, 16, 7], [13, 13, 12, 15, 18, 20, 13, 16, 17, 8, 13, 11, 6, 19, 11, 20, 19, 17, 10, 13, 14, 23, 16, 8], ] shifts_coverage = { "Morning": [0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "Afternoon": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0], "Night": [1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1], "Mixed": [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0], } difference = MinAbsDifference( num_days=2, periods=24, shifts_coverage=shifts_coverage, required_resources=required_resources, max_period_concurrency=27, max_shift_concurrency=25, ) print(difference.solve()["status"]) requirements = MinRequiredResources( num_days=2, periods=24, shifts_coverage=shifts_coverage, required_resources=required_resources, max_period_concurrency=27, max_shift_concurrency=25, ) print(requirements.solve()["status"]) ``` -------------------------------- ### Connecting to scheduling output: MinRequiredResources Source: https://pyworkforce.rodrigo-arenas.com/stable/guide/breaks Shows how to pass the scheduler's output into BreakScheduler's scheduled_resources parameter. The example is incomplete as it only shows the import and the start of the solve call. ```python from pyworkforce.scheduling import MinRequiredResources sched_result = scheduler_obj.solve() ``` -------------------------------- ### Minimal BreakScheduler example with output Source: https://pyworkforce.rodrigo-arenas.com/stable/guide/breaks Configures a BreakScheduler for two shifts over one day, defines a Lunch break with constraints, and prints the optimal break schedule. The output shows the status and per-slot break assignments. ```python from pyworkforce.breaks import BreakScheduler # Two 8-period shifts running on 1 day; three agents on Morning, two on Afternoon shifts_coverage = { "Morning": [1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0], "Afternoon": [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1], } scheduled_resources = {"Morning": [3], "Afternoon": [2]} breaks = [ { "name": "Lunch", "duration_periods": 2, "min_start_after": 2, # must start at least 2 periods into the shift "max_end_before": 2, # must end at least 2 periods before the shift ends } ] # At every period at least this many agents must NOT be on break min_coverage = [[2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1]] scheduler = BreakScheduler( num_days=1, periods=16, shifts_coverage=shifts_coverage, scheduled_resources=scheduled_resources, breaks=breaks, min_coverage=min_coverage, ) result = scheduler.solve() print(result["status"]) for entry in result["break_schedule"]: print(entry) ``` ```text OPTIMAL {'shift': 'Morning', 'day': 0, 'slot': 0, 'break_name': 'Lunch', 'start_period': 2, 'end_period': 4} {'shift': 'Morning', 'day': 0, 'slot': 1, 'break_name': 'Lunch', 'start_period': 4, 'end_period': 6} {'shift': 'Morning', 'day': 0, 'slot': 2, 'break_name': 'Lunch', 'start_period': 2, 'end_period': 4} {'shift': 'Afternoon', 'day': 0, 'slot': 0, 'break_name': 'Lunch', 'start_period': 10, 'end_period': 12} {'shift': 'Afternoon', 'day': 0, 'slot': 1, 'break_name': 'Lunch', 'start_period': 12, 'end_period': 14} ``` -------------------------------- ### Basic ErlangB usage with required_positions Source: https://pyworkforce.rodrigo-arenas.com/stable/guide/erlangb Initializes ErlangB with transactions, aht, interval, and shrinkage, then calls required_positions with max_blocking to get the required positions. The result includes raw_positions, positions, blocking_probability, and occupancy. ```python from pyworkforce.queuing import ErlangB erlang = ErlangB(transactions=100, aht=3, interval=30, shrinkage=0.3) result = erlang.required_positions(max_blocking=0.02) print(result) ``` ```text {'raw_positions': 17, 'positions': 25, 'blocking_probability': 0.0183..., 'occupancy': 0.937...} ``` -------------------------------- ### Inspecting the ErlangC estimator via repr and get_params Source: https://pyworkforce.rodrigo-arenas.com/stable/guide/erlangc Shows the readable representation of an ErlangC instance and the dictionary returned by get_params(). Both display the constructor parameters (transactions, aht, asa, interval, shrinkage) with asa shown as 0.333... in the example. ```python >>> erlang ErlangC(transactions=100, aht=3, asa=0.333..., interval=30, shrinkage=0.3) >>> erlang.get_params() {'transactions': 100, 'aht': 3, 'asa': 0.333..., 'interval': 30, 'shrinkage': 0.3} ``` -------------------------------- ### MultiSkillStaffing optimal skill-profile mix Source: https://pyworkforce.rodrigo-arenas.com/stable/guide/end-to-end Finds the cheapest combination of dedicated and flexible agents to cover two skills (General and Technical) at peak hour. Uses the MultiSkillStaffing class with a list of profiles, each with a name, skills, and cost. The example assumes 13 General agents and 5 Technical agents are needed at the same peak. ```python from pyworkforce.staffing import MultiSkillStaffing skills = ["General", "Technical"] profiles = [ {"name": "General_only", "skills": ["General"], "cost": 1.0}, {"name": "Technical_only","skills": ["Technical"], "cost": 1.0}, {"name": "Flexible", "skills": ["General", "Technical"], "cost": 1.4}, ] ``` -------------------------------- ### Basic Erlang C usage - computing required positions Source: https://pyworkforce.rodrigo-arenas.com/stable/guide/erlangc Imports ErlangC, creates an instance with transactions, service target (asa), handle time (aht), interval and shrinkage, then calls required_positions with a service level target and max occupancy. All time units (asa, aht, interval) must be in the same time unit; the example uses minutes with a 20-second target expressed as 20/60. ```python from pyworkforce.queuing import ErlangC erlang = ErlangC(transactions=100, asa=20 / 60, aht=3, interval=30, shrinkage=0.3) requirements = erlang.required_positions(service_level=0.8, max_occupancy=0.85) ``` ```text {'raw_positions': 14, 'positions': 20, 'service_level': 0.8883500191794669, 'occupancy': 0.7142857142857143, 'waiting_probability': 0.1741319335950498} ``` -------------------------------- ### Scheduling people onto shifts with MinRequiredResources Source: https://pyworkforce.rodrigo-arenas.com/stable/guide/quickstart Uses MinRequiredResources to schedule resources across two days with 24 periods each, given required resources per hour and shift coverage. The example shows the required_resources list and the solver call. ```python from pyworkforce.scheduling import MinRequiredResources # Required resources per hour, one row per day. required_resources = [ [3, 3, 3, 3, 3, 3, 6, 6, 6, 6, 8, 8, 8, 8, 6, 6, 6, 6, 4, 4, 4, 4, 3, 3], [4, 4, 4, 4, 4, 4, 7, 7, 7, 7, 9, 9, 9, 9, 7, 7, 7, 7, 5, 5, 5, 5, 4, 4], ] scheduler = MinRequiredResources( num_days=2, periods=24, shifts_coverage=shifts_coverage, required_resources=required_resources, max_period_concurrency=30, max_shift_concurrency=30, ) solution = scheduler.solve() print(solution["status"], solution["cost"]) ``` -------------------------------- ### Weighting shifts with cost_dict for MinRequiredResources Source: https://pyworkforce.rodrigo-arenas.com/stable/guide/scheduling Shows how to pass a cost_dict to MinRequiredResources to make some shifts more expensive. The cost_dict must contain exactly the same shift names as shifts_coverage. The example assigns higher costs to night and mixed shifts. ```python cost_dict = {"Morning": 8, "Afternoon": 8, "Night": 10, "Mixed": 7} scheduler = MinRequiredResources( num_days=2, periods=24, shifts_coverage=shifts_coverage, required_resources=required_resources, cost_dict=cost_dict, max_period_concurrency=27, max_shift_concurrency=25, ) ``` -------------------------------- ### MultiSkillStaffing.solve() Source: https://pyworkforce.rodrigo-arenas.com/stable/api/staffing Runs the CP-SAT integer programme to find the optimal agent mix and returns a dictionary with the solution status, cost, total agents, agents per profile, and skill coverage. ```APIDOC ## MultiSkillStaffing.solve() ### Description Runs the CP-SAT integer programme to find the optimal agent mix and returns a dictionary with the solution status, cost, total agents, agents per profile, and skill coverage. ### Method solve ### Endpoint MultiSkillStaffing.solve() ### Parameters None ### Request Example ```python result = solver.solve() ``` ### Response #### Success Response (200) - **status** (str) - `"OPTIMAL"`, `"FEASIBLE"`, or `"INFEASIBLE"`. - **cost** (float) - Objective value (weighted headcount); `-1` when infeasible. - **total_agents** (int) - Sum of agents across all profiles; `-1` when infeasible. - **agents_per_profile** (list[dict]) - One `{"profile": name, "agents": n}` entry per profile. - **skill_coverage** (dict) - `{"skill": n}` total agents covering each skill in the solution. #### Response Example ```json { "status": "OPTIMAL", "cost": 10.0, "total_agents": 5, "agents_per_profile": [ {"profile": "profile1", "agents": 3}, {"profile": "profile2", "agents": 2} ], "skill_coverage": { "English": 5, "Billing": 3 } } ``` ``` -------------------------------- ### MultiSkillStaffing basic usage and output Source: https://pyworkforce.rodrigo-arenas.com/stable/guide/staffing Basic usage of MultiSkillStaffing to solve the staffing integer programme. The output shows an optimal solution with 5 agents and cost 6.5, using 3 flexible agents and 2 billing-only agents. The pure-dedicated alternative (5 Billing_only + 3 Technical_only = 8 agents, cost 8.0) costs more because flexible agents count towards both skills simultaneously. ```python from pyworkforce.staffing import MultiSkillStaffing skills = ["Billing", "Technical"] profiles = [ {"name": "Billing_only", "skills": ["Billing"], "cost": 1.0}, {"name": "Technical_only", "skills": ["Technical"], "cost": 1.0}, {"name": "Flexible", "skills": ["Billing", "Technical"], "cost": 1.5}, ] required = {"Billing": 5, "Technical": 3} ms = MultiSkillStaffing(skills=skills, profiles=profiles, required_positions=required) result = ms.solve() print(result) ``` ```text {'status': 'OPTIMAL', 'cost': 6.5, 'total_agents': 5, 'agents_per_profile': [ {'profile': 'Billing_only', 'agents': 2}, {'profile': 'Technical_only', 'agents': 0}, {'profile': 'Flexible', 'agents': 3}, ], 'skill_coverage': {'Billing': 5, 'Technical': 3}} ``` -------------------------------- ### Queuing and staffing with ErlangC, ErlangA, and MultiSkillStaffing Source: https://pyworkforce.rodrigo-arenas.com/stable/guide/staffing Computes required positions for Billing and Technical skills using ErlangC and ErlangA, then solves the multi-skill staffing problem with three profiles. The printed output shows the required positions and the optimal agent allocation. ```python billing = ErlangC( transactions=100, aht=4, asa=20 / 60, interval=60, shrinkage=0.3 ) technical = ErlangA( transactions=50, aht=8, asa=20 / 60, interval=60, patience=10, shrinkage=0.3 ) required = { "Billing": billing.required_positions(service_level=0.8)["raw_positions"], "Technical": technical.required_positions(service_level=0.8)["raw_positions"], } print("Required:", required) ``` ```python profiles = [ {"name": "Billing_only", "skills": ["Billing"], "cost": 1.0}, {"name": "Technical_only", "skills": ["Technical"], "cost": 1.0}, {"name": "Flexible", "skills": ["Billing", "Technical"], "cost": 1.4}, ] ms = MultiSkillStaffing( skills=list(required), profiles=profiles, required_positions=required ) result = ms.solve() print(f"Total agents: {result['total_agents']} cost: {result['cost']:.1f}") for entry in result["agents_per_profile"]: if entry["agents"] > 0: print(f" {entry['profile']:20s}: {entry['agents']}") ``` ```text Required: {'Billing': 13, 'Technical': 6} Total agents: 13 cost: 15.4 Billing_only : 7 Flexible : 6 ``` -------------------------------- ### Rostering named people with MinHoursRoster Source: https://pyworkforce.rodrigo-arenas.com/stable/guide/quickstart Uses MinHoursRoster to assign named resources to shifts with minimum working hours and restrictions. The example bans a specific shift for a resource and prints the solution status. ```python from pyworkforce.rostering import MinHoursRoster roster = MinHoursRoster( num_days=2, resources=["a@co", "b@co", "c@co"], shifts=["Morning", "Night"], shifts_hours=[8, 8], min_working_hours=8, banned_shifts=[{"resource": "a@co", "shift": "Night", "day": 0}], max_resting=1, required_resources={"Morning": [1, 1], "Night": [1, 1]}, ) solution = roster.solve() print(solution["status"]) ``` -------------------------------- ### Staffing a queue with Erlang C Source: https://pyworkforce.rodrigo-arenas.com/stable/guide/quickstart Uses ErlangC to compute required agents for 100 calls in a 30-minute interval with 3-minute average handle time, 80% service level within 20 seconds, and 30% shrinkage. Output shows raw_positions and positions including shrinkage allowance. ```python from pyworkforce.queuing import ErlangC erlang = ErlangC(transactions=100, asa=20 / 60, aht=3, interval=30, shrinkage=0.3) requirements = erlang.required_positions(service_level=0.8, max_occupancy=0.85) print(requirements) ``` ```text {'raw_positions': 14, 'positions': 20, 'service_level': 0.8883500191794669, 'occupancy': 0.7142857142857143, 'waiting_probability': 0.1741319335950498} ``` -------------------------------- ### MultiSkillStaffing constructor signature Source: https://pyworkforce.rodrigo-arenas.com/stable/api/staffing Shows the constructor signature for the MultiSkillStaffing class. Use this to instantiate the optimizer with skills, profiles, required_positions, and optional solver parameters. The constructor raises ValueError for invalid inputs as documented. ```python MultiSkillStaffing( skills, profiles, required_positions, max_agents=None, max_search_time=60.0, num_search_workers=2, ) ``` -------------------------------- ### Scalar metrics across positions with MultiErlangC service_level Source: https://pyworkforce.rodrigo-arenas.com/stable/guide/scenarios Use the MultiErlangC.service_level method with a grid of positions to get scalar results. The output places the scalar result in a 'result' column, which can be used for plotting staffing curves or exporting to CSV. ```python from pyworkforce.queuing import MultiErlangC from pyworkforce.utils import results_to_dataframe multi = MultiErlangC( param_grid={"transactions": [100], "aht": [3], "interval": [30], "asa": [20 / 60], "shrinkage": [0.0]}, n_jobs=1, ) sl = multi.service_level({"positions": [11, 12, 13, 14]}) df = results_to_dataframe(sl, multi.service_level_params) print(df[["positions", "result"]].round(4).to_string(index=False)) ``` ```text positions result 11 0.3896 12 0.6402 13 0.7956 14 0.8884 ``` -------------------------------- ### MultiSkillStaffing.get_params() Source: https://pyworkforce.rodrigo-arenas.com/stable/api/staffing Returns the parameters of the MultiSkillStaffing instance as a dictionary. ```APIDOC ## MultiSkillStaffing.get_params() ### Description Returns the parameters of the MultiSkillStaffing instance as a dictionary. ### Method get_params ### Endpoint MultiSkillStaffing.get_params() ### Parameters None ### Request Example ```python params = solver.get_params() ``` ### Response #### Success Response (200) - **skills** (list[str]) - Names of the distinct skill types. - **profiles** (list[dict]) - Agent skill profiles. - **required_positions** (dict) - Minimum agents needed for each skill. - **max_agents** (int, optional) - Hard cap on total agents. - **max_search_time** (float) - Maximum solver wall-clock time in seconds. - **num_search_workers** (int) - Number of parallel search workers. #### Response Example ```json { "skills": ["English", "Billing"], "profiles": [ {"name": "profile1", "skills": ["English"], "cost": 1.0} ], "required_positions": {"English": 2}, "max_agents": null, "max_search_time": 60.0, "num_search_workers": 2 } ``` ``` -------------------------------- ### Import ErlangC, ErlangA, and MultiSkillStaffing Source: https://pyworkforce.rodrigo-arenas.com/stable/guide/staffing Import statements for connecting MultiSkillStaffing to Erlang C or Erlang A output. Pass raw_positions directly from the queuing step. ```python from pyworkforce.queuing import ErlangC, ErlangA from pyworkforce.staffing import MultiSkillStaffing ``` -------------------------------- ### Solve multi-skill staffing with MultiSkillStaffing Source: https://pyworkforce.rodrigo-arenas.com/stable/guide/end-to-end Defines required positions per skill and solves the staffing problem. Prints status, total agents, cost, and the agent count per profile. The output shows an optimal solution with 13 total agents and a cost of 16.0. ```python # Peak-hour requirement (13 General from Step 1; 5 Technical specialists) required = {"General": 13, "Technical": 5} ms = MultiSkillStaffing( skills=skills, profiles=profiles, required_positions=required ) mix = ms.solve() print("status:", mix["status"], "| total:", mix["total_agents"], "| cost:", mix["cost"]) for entry in mix["agents_per_profile"]: if entry["agents"] > 0: print(f" {entry['profile']:16s}: {entry['agents']}") ``` ```text status: OPTIMAL | total: 13 | cost: 16.0 General_only : 8 Flexible : 5 ``` -------------------------------- ### Roster named agents with MinHoursRoster Source: https://pyworkforce.rodrigo-arenas.com/stable/guide/end-to-end Assigns named agents to shifts and days using MinHoursRoster. The example shows a small self-contained roster with a banned shift and a preference being respected. The output shows the status, shifted hours, resting days, and the assignment list. ```python from pyworkforce.rostering import MinHoursRoster roster = MinHoursRoster( num_days=2, resources=["ana@co", "ben@co", "cara@co", "dan@co", "eve@co", "finn@co", "gil@co"], shifts=["Early", "Late", "Night"], shifts_hours=[8, 8, 9], min_working_hours=8, max_resting=1, required_resources={"Early": [2, 2], "Late": [2, 2], "Night": [1, 1]}, banned_shifts=[{"resource": "ana@co", "shift": "Night", "day": 0}], resources_preferences=[{"resource": "ana@co", "shift": "Early"}], ) sol = roster.solve() print("status:", sol["status"], "| shifted_hours:", sol["shifted_hours"], "| resting_days:", sol["resting_days"]) for a in sol["resource_shifts"]: print(a) ``` ```text status: OPTIMAL | shifted_hours: 82 | resting_days: 4 {'resource': 'ana@co', 'day': 0, 'shift': 'Early'} {'resource': 'ana@co', 'day': 1, 'shift': 'Early'} {'resource': 'ben@co', 'day': 1, 'shift': 'Night'} {'resource': 'cara@co', 'day': 0, 'shift': 'Early'} {'resource': 'cara@co', 'day': 1, 'shift': 'Early'} {'resource': 'dan@co', 'day': 0, 'shift': 'Late'} {'resource': 'eve@co', 'day': 0, 'shift': 'Late'} {'resource': 'finn@co', 'day': 0, 'shift': 'Night'} {'resource': 'finn@co', 'day': 1, 'shift': 'Late'} {'resource': 'gil@co', 'day': 1, 'shift': 'Late'} ``` -------------------------------- ### ErlangA constructor Source: https://pyworkforce.rodrigo-arenas.com/stable/api/queuing Creates an Erlang A (M/M/c+M) queueing model with customer abandonment. Extends ErlangC parameters with a patience argument for mean time before abandonment. All metrics are computed exactly from the birth-death stationary distribution. ```python ErlangA(transactions, aht, asa, interval, patience, shrinkage=0.0) ``` -------------------------------- ### BreakScheduler Source: https://pyworkforce.rodrigo-arenas.com/stable/api/breaks Schedules breaks for agent slots within each shift to maintain minimum coverage throughout the planning horizon. Each shift/day combination has scheduled_resources[shift][day] anonymous agent slots; the solver assigns a start period to every break for every slot while enforcing break windows, non-overlap, and coverage constraints. ```APIDOC ## BreakScheduler ### Description Schedules breaks for agent slots within each shift to maintain minimum coverage throughout the planning horizon. Each shift/day combination has `scheduled_resources[shift][day]` anonymous agent slots; the solver assigns a start period to every break for every slot while enforcing break windows, non-overlap, and coverage constraints. ### Constructor ```python BreakScheduler( num_days, periods, shifts_coverage, scheduled_resources, breaks, min_coverage, max_search_time=120.0, num_search_workers=2, ) ``` ### Parameters - **num_days** (`int`) - Required - Number of days in the planning horizon. - **periods** (`int`) - Required - Number of periods per day (e.g. `48` for 30-minute slots in a 24-hour day). - **shifts_coverage** (`dict`) - Required - `{"shift_name": coverage_array}` where `coverage_array` has length `periods` and uses `1` when the shift is active, `0` otherwise. - **scheduled_resources** (`dict`) - Required - `{"shift_name": [count_day0, count_day1, …]}` with the number of agent slots per shift per day (typically the output of the scheduling step). - **breaks** (`list[dict]`) - Required - Break definitions. Each dict must have: - `name` (`str`) - Required - Unique identifier. - `duration_periods` (`int`) - Required - Break length in periods. - `min_start_after` (`int`, default `0`) - Optional - Periods into the shift before the break may start. - `max_end_before` (`int`, default `0`) - Optional - Periods remaining in the shift after the break must finish. - **min_coverage** (`list`) - Required - 2-D array `[num_days][periods]` - minimum agents that must remain available (not on break) at each period. - **max_search_time** (`float`, default `120.0`) - Optional - Maximum solver wall-clock time in seconds. - **num_search_workers** (`int`, default `2`) - Optional - Number of parallel search workers. ### Attributes - **solution_** (`dict` or `None`) - The last result returned by `solve()`, or `None` if `solve()` has not been called. ### Methods #### solve() → dict ##### Description Run the CP-SAT solver and return a result dict with status, cost, and break schedule. ##### Returns - **status** (`str`) - `"OPTIMAL"`, `"FEASIBLE"`, or `"INFEASIBLE"`. - **cost** (`float`) - Objective value (`0.0` for optimal/feasible; `-1` when infeasible). - **break_schedule** (`list[dict]`) - One entry per `(shift, day, slot, break)`. Each entry contains `shift`, `day`, `slot`, `break_name`, `start_period` (inclusive) and `end_period` (exclusive). #### get_params() → dict ##### Description Returns the parameters of the BreakScheduler instance. ``` -------------------------------- ### ErlangC constructor Source: https://pyworkforce.rodrigo-arenas.com/stable/api/queuing Creates an Erlang C (M/M/c) queueing model with Poisson arrivals, exponential handling times, infinite queue, and no abandonment. Parameters include transactions, aht, asa, interval, and optional shrinkage (default 0.0). ```python ErlangC(transactions, aht, asa, interval, shrinkage=0.0) ``` -------------------------------- ### MultiErlangB parameter sweep with results_to_dataframe Source: https://pyworkforce.rodrigo-arenas.com/stable/guide/erlangb Runs a parameter grid in parallel using MultiErlangB, then converts results to a DataFrame and prints selected columns. The output shows blocking probabilities for different transaction counts. ```python from pyworkforce.queuing import MultiErlangB from pyworkforce.utils import results_to_dataframe param_grid = { "transactions": [80, 100, 120], "aht": [3], "interval": [30], "shrinkage": [0.0], } multi = MultiErlangB(param_grid=param_grid, n_jobs=-1) scenarios = {"max_blocking": [0.01, 0.02]} results = multi.required_positions(scenarios) df = results_to_dataframe(results, multi.required_positions_params) print(df[["transactions", "max_blocking", "raw_positions", "blocking_probability"]].to_string(index=False)) ``` ```text transactions max_blocking raw_positions blocking_probability 80 0.01 14 0.0098... 80 0.02 13 0.0190... 100 0.01 17 0.0095... 100 0.02 17 0.0095... 120 0.01 20 0.0099... 120 0.02 19 0.0191... ``` -------------------------------- ### Evaluating metrics for a fixed number of positions Source: https://pyworkforce.rodrigo-arenas.com/stable/guide/erlangc Calls waiting_probability, service_level, and achieved_occupancy on an existing ErlangC instance, each with positions=20. These return the probability of waiting, the achieved service level, and the achieved occupancy for that fixed staffing level. ```python erlang.waiting_probability(positions=20) # P(wait) erlang.service_level(positions=20) # achieved service level erlang.achieved_occupancy(positions=20) # occupancy ``` -------------------------------- ### MultiErlangA constructor Source: https://pyworkforce.rodrigo-arenas.com/stable/api/queuing Creates a MultiErlangA instance for parallel evaluation of ErlangA over a parameter grid. param_grid takes ErlangA constructor arguments including patience. n_jobs and pre_dispatch control parallelism as in MultiErlangC. ```python MultiErlangA(param_grid, n_jobs=2, pre_dispatch='2 * n_jobs') ``` -------------------------------- ### MinHoursRoster Source: https://pyworkforce.rodrigo-arenas.com/stable/api/rostering Initializes a rostering problem with resources, shifts, and constraints. Assigns resources to days and shifts while minimizing total scheduled hours. ```APIDOC ## MinHoursRoster ### Description Assigns named resources to days and shifts, minimizing total scheduled hours while respecting the configured rules and rewarding preferences. ### Constructor ```python MinHoursRoster(num_days, resources, shifts, shifts_hours, min_working_hours, banned_shifts, max_resting, required_resources, non_sequential_shifts=None, resources_preferences=None, resources_prioritization=None, max_search_time=240, num_search_workers=2) ``` ### Parameters - **num_days** (`int`) - Required - Days to schedule. - **resources** (`list[str]`) - Required - Unique resource names. - **shifts** (`list[str]`) - Required - Shift names. - **shifts_hours** (`list`) - Required - Duration of each shift (same order as `shifts`). - **min_working_hours** (`int`) - Required - Minimum hours per resource over the horizon. - **banned_shifts** (`list[dict]`) - Required - `{"resource", "shift", "day"}` entries that forbid an assignment. May be `None`. - **max_resting** (`int`) - Required - Maximum resting days per resource; must be `< num_days`. - **required_resources** (`dict`) - Required - `{shift_name: [per-day requirement, …]}`, one entry per day for every shift. - **non_sequential_shifts** (`list[dict]`, optional) - `{"origin", "destination"}` pairs that cannot occur on consecutive days. - **resources_preferences** (`list[dict]`, optional) - `{"resource", "shift"}` preferred assignments. - **resources_prioritization** (`list[dict]`, optional) - `{"resource", "weight"}` relative preference weights. - **max_search_time** (`float`) - Solver time limit, seconds. Default: `240`. - **num_search_workers** (`int`) - Solver worker threads. Default: `2`. ### Validation Raises `ValueError` when: - `resources` are not unique - `shifts` and `shifts_hours` differ in length - `required_resources` is missing a shift or has the wrong number of days - `max_resting >= num_days` ``` -------------------------------- ### Schedule shifts with MinRequiredResources Source: https://pyworkforce.rodrigo-arenas.com/stable/guide/end-to-end Creates a scheduler to determine how many people are needed per shift to cover all hours. The required_resources parameter takes the per-hour positions from Step 1. The max_period_concurrency and max_shift_concurrency are upper bounds; if too small, the problem becomes INFEASIBLE. ```python from pyworkforce.scheduling import MinRequiredResources scheduler = MinRequiredResources( num_days=1, periods=24, shifts_coverage=shifts_coverage, required_resources=[required_c], max_period_concurrency=40, max_shift_concurrency=30, ) schedule = scheduler.solve() print("status:", schedule["status"], "| total agents (cost):", schedule["cost"]) for item in schedule["resources_shifts"]: print(item) ``` ```text status: OPTIMAL | total agents (cost): 30.0 {'day': 0, 'shift': 'Early', 'resources': 12} {'day': 0, 'shift': 'Day', 'resources': 1} {'day': 0, 'shift': 'Late', 'resources': 11} {'day': 0, 'shift': 'Night', 'resources': 6} ``` -------------------------------- ### Per-position metrics for ErlangA Source: https://pyworkforce.rodrigo-arenas.com/stable/guide/erlanga For a fixed number of positions, these methods return individual metrics: waiting probability, abandonment probability, achieved occupancy, average speed of answer, average queue length, and service level. Each method takes the positions argument. ```python erlang.waiting_probability(positions=14) # P(delayed) erlang.abandonment_probability(positions=14) # P(customer abandons) erlang.achieved_occupancy(positions=14) # server utilization erlang.average_speed_of_answer(positions=14) # expected wait (minutes) erlang.average_queue_length(positions=14) # expected number waiting erlang.service_level(positions=14) # answered within asa ``` -------------------------------- ### solve() Source: https://pyworkforce.rodrigo-arenas.com/stable/api/rostering Solves the rostering problem and returns a dictionary with the solution, including assignments and resting days. ```APIDOC ## solve() ### Description Solves the rostering problem and returns a dictionary with the solution. The result is also stored in the `solution_` attribute. ### Returns Returns a `dict` with the following keys: - **status** - Solver status. - **cost** - Total cost of the schedule. - **shifted_hours** - Total shifted hours. - **total_resources** - Total number of resources used. - **total_shifts** - Total number of shifts assigned. - **resting_days** - Total resting days. - **resource_shifts** - List of `{"resource", "day", "shift"}` assignments. - **resting_resource** - List of `{"resource", "day"}` resting entries. ``` -------------------------------- ### Capping total headcount with max_agents Source: https://pyworkforce.rodrigo-arenas.com/stable/guide/staffing Passes max_agents to MultiSkillStaffing to impose a hard budget on total agents. The solve() status becomes "INFEASIBLE" if the cap cannot cover all requirements. ```python ms = MultiSkillStaffing( skills=skills, profiles=profiles, required_positions=required, max_agents=12, ) result = ms.solve() print(result["status"]) # "INFEASIBLE" if 12 cannot cover all requirements ``` -------------------------------- ### Erlang C and Erlang A required positions per hour Source: https://pyworkforce.rodrigo-arenas.com/stable/guide/end-to-end Computes required agent positions per hour for a single day using Erlang C and Erlang A queuing models. Uses a forecast of calls per hour, with aht=4, asa=20/60, interval=60, shrinkage=0.3, and for Erlang A also patience=8. Prints the required positions and total agent-hours for both models. ```python from pyworkforce.queuing import ErlangC, ErlangA calls_per_hour = [ 12, 8, 6, 5, 5, 8, 20, 45, 70, 85, 90, 88, 80, 78, 82, 80, 70, 60, 48, 40, 32, 26, 20, 15, ] required_c, required_a = [], [] for calls in calls_per_hour: c = ErlangC(transactions=calls, aht=4, asa=20 / 60, interval=60, shrinkage=0.3) required_c.append(c.required_positions(service_level=0.8, max_occupancy=0.85)["positions"]) a = ErlangA(transactions=calls, aht=4, asa=20 / 60, interval=60, patience=8, shrinkage=0.3) required_a.append(a.required_positions(service_level=0.8, max_occupancy=0.85)["positions"]) print("Erlang C:", required_c) print("Erlang A:", required_a) print("Total agent-hours C:", sum(required_c), " A:", sum(required_a)) ``` ```text Erlang C: [5, 3, 3, 3, 3, 3, 5, 8, 12, 13, 13, 13, 12, 12, 12, 12, 12, 10, 9, 8, 6, 6, 5, 5] Erlang A: [3, 3, 3, 3, 3, 3, 5, 8, 10, 12, 13, 13, 12, 12, 12, 12, 10, 9, 8, 8, 6, 6, 5, 5] Total agent-hours C: 193 A: 184 ``` -------------------------------- ### MinRequiredResources constructor signature Source: https://pyworkforce.rodrigo-arenas.com/stable/api/scheduling Constructor signature for the MinRequiredResources scheduler. Minimizes the (optionally weighted) number of scheduled resources while covering every period. The cost_dict parameter is optional and defaults to a cost of 1 per shift. ```python MinRequiredResources(num_days, periods, shifts_coverage, required_resources, max_period_concurrency, max_shift_concurrency, cost_dict=None, max_search_time=240.0, num_search_workers=2) ``` -------------------------------- ### Basic usage: ErlangA required_positions Source: https://pyworkforce.rodrigo-arenas.com/stable/guide/erlanga Creates an ErlangA instance with transactions, aht, asa, interval, patience, and shrinkage, then calls required_positions to find the smallest number of positions meeting service level, max occupancy, and max abandonment targets. The output shows raw_positions, positions, and resulting metrics. ```python from pyworkforce.queuing import ErlangA erlang = ErlangA(transactions=100, aht=3, asa=20 / 60, interval=30, patience=5, shrinkage=0.3) print(erlang.required_positions(service_level=0.8, max_occupancy=0.85, max_abandonment=0.05)) ``` ```text {'raw_positions': 13, 'positions': 19, 'service_level': 0.858..., 'occupancy': 0.750..., 'abandonment_probability': 0.025..., 'waiting_probability': 0.226..., 'average_speed_of_answer': 0.125...} ``` -------------------------------- ### Sweep patience with MultiErlangA and results_to_dataframe Source: https://pyworkforce.rodrigo-arenas.com/stable/guide/scenarios Sweep the patience parameter across multiple values to see how customer impatience affects required staffing. The output shows that as patience increases, abandonment probability falls while the achieved service level drops slightly for the same staffing. ```python from pyworkforce.queuing import MultiErlangA from pyworkforce.utils import results_to_dataframe grid = { "transactions": [100], "aht": [3], "interval": [30], "asa": [20 / 60], "patience": [3, 5, 10], "shrinkage": [0.3], } multi = MultiErlangA(param_grid=grid, n_jobs=1) results = multi.required_positions( {"service_level": [0.8], "max_occupancy": [0.85], "max_abandonment": [0.05]} ) df = results_to_dataframe(results, multi.required_positions_params) cols = ["patience", "raw_positions", "positions", "service_level_result", "abandonment_probability"] print(df[cols].round(4).to_string(index=False)) ``` ```text patience raw_positions positions service_level_result abandonment_probability 3 13 19 0.8751 0.0322 5 13 19 0.8582 0.0250 10 13 19 0.8376 0.0165 ``` -------------------------------- ### MultiErlangC constructor Source: https://pyworkforce.rodrigo-arenas.com/stable/api/queuing Creates a MultiErlangC instance for parallel evaluation of ErlangC over a parameter grid. param_grid maps ErlangC constructor arguments to lists of values. n_jobs controls parallel workers (default 2, -1 for all CPUs), pre_dispatch sets joblib pre-dispatch (default '2 * n_jobs'). ```python MultiErlangC(param_grid, n_jobs=2, pre_dispatch='2 * n_jobs') ```