Skip to main content

Common tools for workforce management, schedule and optimization problems

Project description

Build Status Codecov PyPI Version Python Version

pyworkforce

Tools for workforce management problems such as queue staffing, shift scheduling, rostering, and operations research optimization.

The full documentation is available at pyworkforce.readthedocs.io.

Installation

We recommend installing pyworkforce in a virtual environment:

pip install pyworkforce

pyworkforce supports Python 3.12, 3.13, and 3.14.

If you are using Anaconda and run into installation issues, update the environment first:

conda update --all

If the issue is related to OR-Tools, check the OR-Tools installation guide.

For runnable examples, see the examples folder.

What pyworkforce Does

pyworkforce is organized around three planning steps:

Queuing

Use pyworkforce.queuing when you need to estimate how many resources are required to handle incoming work, for example calls arriving at a call center. The current implementation uses Erlang C assumptions: constant arrival rate, infinite queue, and no customer dropout.

queue_system

  • queuing.ErlangC: Calculate staffing requirements and performance metrics for one queue scenario.
  • queuing.MultiErlangC: Run multiple Erlang C scenarios from a parameter grid.

Scheduling

Use pyworkforce.scheduling when you already know the required resources by time interval and need to choose how many people to place on each predefined shift.

  • scheduling.MinAbsDifference: Minimizes the total absolute difference between required and scheduled resources.
  • scheduling.MinRequiredResources: Minimizes the total weighted number of scheduled resources while ensuring every interval is covered.

Rostering

Use pyworkforce.rostering when you have named resources and need to assign them to days and shifts while respecting rules such as banned shifts, rest days, minimum working hours, and preferences.

  • rostering.MinHoursRoster: Builds a resource-level roster that covers shift requirements with the minimum scheduled hours.

Queue systems:

A brief introduction can be found in this medium post

Example:

from pyworkforce.queuing import ErlangC

erlang = ErlangC(transactions=100, asa=20/60, aht=3, interval=30, shrinkage=0.3)

positions_requirements = erlang.required_positions(service_level=0.8, max_occupancy=0.85)
print("positions_requirements: ", positions_requirements)

Output:

>> positions_requirements:  {'raw_positions': 14, 
                             'positions': 20, 
                             'service_level': 0.8883500191794669, 
                             'occupancy': 0.7142857142857143, 
                             'waiting_probability': 0.1741319335950498}

If you want to run several scenarios at the same time, use MultiErlangC. For example, this tries different service-level targets:

from pyworkforce.queuing import MultiErlangC

param_grid = {"transactions": [100], "aht": [3], "interval": [30], "asa": [20 / 60], "shrinkage": [0.3]}
multi_erlang = MultiErlangC(param_grid=param_grid, n_jobs=-1)

required_positions_scenarios = {"service_level": [0.8, 0.85, 0.9], "max_occupancy": [0.8]}

positions_requirements = multi_erlang.required_positions(required_positions_scenarios)
print("positions_requirements: ", positions_requirements)

Output:

>> positions_requirements:   [
                                {
                                    "raw_positions": 13,
                                    "positions": 19,
                                    "service_level": 0.7955947884177831,
                                    "occupancy": 0.7692307692307693,
                                    "waiting_probability": 0.285270453036493
                                },
                                {
                                    "raw_positions": 14,
                                    "positions": 20,
                                    "service_level": 0.8883500191794669,
                                    "occupancy": 0.7142857142857143,
                                    "waiting_probability": 0.1741319335950498
                                },
                                {
                                    "raw_positions": 15,
                                    "positions": 22,
                                    "service_level": 0.9414528428690223,
                                    "occupancy": 0.6666666666666666,
                                    "waiting_probability": 0.10204236700798798
                                }
                            ]

Scheduling

A brief introduction can be found in this medium post

Example:

from pyworkforce.scheduling import MinAbsDifference, MinRequiredResources

# Rows are days. Each value is the number of required positions for one hour of the day.
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]
]

# Each shift has 24 entries, one per hour. Use 1 if the shift covers that hour, otherwise 0.
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]}

# Method One
difference_scheduler = MinAbsDifference(num_days=2,
                                        periods=24,
                                        shifts_coverage=shifts_coverage,
                                        required_resources=required_resources,
                                        max_period_concurrency=27,
                                        max_shift_concurrency=25)

difference_solution = difference_scheduler.solve()

# Method Two

requirements_scheduler = MinRequiredResources(num_days=2,
                                              periods=24,
                                              shifts_coverage=shifts_coverage,
                                              required_resources=required_resources,
                                              max_period_concurrency=27,
                                              max_shift_concurrency=25)

requirements_solution = requirements_scheduler.solve()

print("difference_solution :", difference_solution)

print("requirements_solution :", requirements_solution)

Output:

>> difference_solution: {'status': 'OPTIMAL', 
                          'cost': 157.0, 
                          'resources_shifts': [{'day': 0, 'shift': 'Morning', 'resources': 8},
                                               {'day': 0, 'shift': 'Afternoon', 'resources': 11},
                                               {'day': 0, 'shift': 'Night', 'resources': 9}, 
                                               {'day': 0, 'shift': 'Mixed', 'resources': 1}, 
                                               {'day': 1, 'shift': 'Morning', 'resources': 13}, 
                                               {'day': 1, 'shift': 'Afternoon', 'resources': 17}, 
                                               {'day': 1, 'shift': 'Night', 'resources': 13}, 
                                               {'day': 1, 'shift': 'Mixed', 'resources': 0}]
                          }

>> requirements_solution: {'status': 'OPTIMAL', 
                           'cost': 113.0, 
                           'resources_shifts': [{'day': 0, 'shift': 'Morning', 'resources': 15}, 
                                                {'day': 0, 'shift': 'Afternoon', 'resources': 13}, 
                                                {'day': 0, 'shift': 'Night', 'resources': 19}, 
                                                {'day': 0, 'shift': 'Mixed', 'resources': 3}, 
                                                {'day': 1, 'shift': 'Morning', 'resources': 20}, 
                                                {'day': 1, 'shift': 'Afternoon', 'resources': 20}, 
                                                {'day': 1, 'shift': 'Night', 'resources': 23}, 
                                                {'day': 1, 'shift': 'Mixed', 'resources': 0}]}

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pyworkforce-0.5.2.tar.gz (20.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pyworkforce-0.5.2-py3-none-any.whl (23.5 kB view details)

Uploaded Python 3

File details

Details for the file pyworkforce-0.5.2.tar.gz.

File metadata

  • Download URL: pyworkforce-0.5.2.tar.gz
  • Upload date:
  • Size: 20.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.6

File hashes

Hashes for pyworkforce-0.5.2.tar.gz
Algorithm Hash digest
SHA256 ae5c9dfcc24a334437e09af97b8a2955edb71711ca32b06e367381505860bd64
MD5 d1576814fa357d7f324523c6f6e4b622
BLAKE2b-256 d9b466e30916c76893293faa9d08805c63b1d672ac37203f8d7679b4b32237b5

See more details on using hashes here.

File details

Details for the file pyworkforce-0.5.2-py3-none-any.whl.

File metadata

  • Download URL: pyworkforce-0.5.2-py3-none-any.whl
  • Upload date:
  • Size: 23.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.6

File hashes

Hashes for pyworkforce-0.5.2-py3-none-any.whl
Algorithm Hash digest
SHA256 df6b252e513e13dffa42dee7b49f5d500166010dcebf7ea0686137b3f726c8cd
MD5 ea130be0d435531e9c7b2b229f5bbf3b
BLAKE2b-256 f40b8d82aa23cdd42a16884f586a844d1ad33ed0e2b586ff7edd5e26d06429b2

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page