{"id":15640759,"url":"https://github.com/rodrigo-arenas/pyworkforce","last_synced_at":"2025-04-09T12:06:50.343Z","repository":{"id":37387729,"uuid":"344803466","full_name":"rodrigo-arenas/pyworkforce","owner":"rodrigo-arenas","description":"Standard tools for workforce management, queuing, scheduling, rostering and optimization problems.","archived":false,"fork":false,"pushed_at":"2024-09-05T14:59:59.000Z","size":143,"stargazers_count":78,"open_issues_count":4,"forks_count":19,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-04-02T09:07:43.524Z","etag":null,"topics":["begginer-friendly","data-science","erlangc","investigation-of-operation","investigations-search","looking-for-contributors","operations-research","optimization","ortools","python","schedule","scheduling-algorithms","up-for-grabs","workforce","workforce-management"],"latest_commit_sha":null,"homepage":"https://pyworkforce.readthedocs.io","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rodrigo-arenas.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-03-05T12:25:44.000Z","updated_at":"2025-03-28T15:05:30.000Z","dependencies_parsed_at":"2024-10-23T05:27:48.102Z","dependency_job_id":null,"html_url":"https://github.com/rodrigo-arenas/pyworkforce","commit_stats":{"total_commits":100,"total_committers":2,"mean_commits":50.0,"dds":0.27,"last_synced_commit":"372d550253d794d607534e35d80ffddf916113b2"},"previous_names":[],"tags_count":9,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rodrigo-arenas%2Fpyworkforce","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rodrigo-arenas%2Fpyworkforce/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rodrigo-arenas%2Fpyworkforce/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rodrigo-arenas%2Fpyworkforce/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rodrigo-arenas","download_url":"https://codeload.github.com/rodrigo-arenas/pyworkforce/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248036063,"owners_count":21037092,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["begginer-friendly","data-science","erlangc","investigation-of-operation","investigations-search","looking-for-contributors","operations-research","optimization","ortools","python","schedule","scheduling-algorithms","up-for-grabs","workforce","workforce-management"],"created_at":"2024-10-03T11:39:46.087Z","updated_at":"2025-04-09T12:06:50.326Z","avatar_url":"https://github.com/rodrigo-arenas.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"\n[![Build Status](https://www.travis-ci.com/rodrigo-arenas/pyworkforce.svg?branch=main)](https://www.travis-ci.com/rodrigo-arenas/pyworkforce)\n[![Codecov](https://codecov.io/gh/rodrigo-arenas/pyworkforce/branch/main/graphs/badge.svg?branch=main\u0026service=github)](https://codecov.io/github/rodrigo-arenas/pyworkforce?branch=main)\n[![PyPI Version](https://badge.fury.io/py/pyworkforce.svg)](https://badge.fury.io/py/pyworkforce)\n[![Python Version](https://img.shields.io/badge/python-3.8%20%7C%203.9%20%7C%203.10%20%7C%203.11-blue)](https://www.python.org/downloads/)\n\n\n# pyworkforce\nStandard tools for workforce management, queuing, scheduling, rostering and optimization problems.\n\nMake sure to check the documentation, which is available [here](https://pyworkforce.readthedocs.io/en/stable/)\n\n# Usage:\nInstall pyworkforce\n\nIt's advised to install pyworkforce using a virtual env, inside the env use:\n\n```\npip install pyworkforce\n```\n\nIf you are using anaconda an having some issue on the installation, try running first\n\n```\nconda update --all\n```\n\nIf you are having trouble with or-tools installation, check the [or-tools guide](https://github.com/google/or-tools#installation)\n\nFor a complete list and details of examples go to the \n[examples folder](https://github.com/rodrigo-arenas/pyworkforce/tree/develop/examples)\n\n## Features:\npyworkforce currently includes:\n\n### Queuing\nIt solves the following system resource requirements:\n\n![queue_system](https://raw.githubusercontent.com/rodrigo-arenas/pyworkforce/main/docs/images/erlangc_queue_system.png)\n\n- **queuing.ErlangC:** Find the number of resources required to attend incoming traffic to a constant rate, \n  infinite queue length, and no dropout.\n  \n### Scheduling\n\nIt finds the number of resources to schedule in a shift based on the number of required positions per time interval \n(found, for example, using  [queuing.ErlangC](./pyworkforce/queuing/erlang.py)), maximum capacity restrictions, and static shifts coverage.\u003cbr\u003e\n- **scheduling.MinAbsDifference:** This module finds the \"optimal\" assignation by minimizing the total absolute \n    differences between required resources per interval against the scheduled resources found by the solver.\n- **scheduling.MinRequiredResources**: This module finds the \"optimal\" assignation by minimizing the total \n    weighted amount of scheduled resources (optionally weighted by shift cost), it ensures that in all intervals, there are\n    never fewer resources shifted than the ones required per period.\n\n### Rostering\n\nIt assigns a list of resources to a list of required positions per day and shifts; it takes into account\ndifferent restrictions as shift bans, consecutive shifts, resting days, and others.\nIt also introduces soft restrictions like shift preferences.\n\n### Queue systems:\n\nA brief introduction can be found in this [medium post](https://towardsdatascience.com/workforce-planning-optimization-using-python-69af0ef9011a)\n\n#### Example:\n\n```python\nfrom pyworkforce.queuing import ErlangC\n\nerlang = ErlangC(transactions=100, asa=20/60, aht=3, interval=30, shrinkage=0.3)\n\npositions_requirements = erlang.required_positions(service_level=0.8, max_occupancy=0.85)\nprint(\"positions_requirements: \", positions_requirements)\n```\nOutput:\n```\n\u003e\u003e positions_requirements:  {'raw_positions': 14, \n                             'positions': 20, \n                             'service_level': 0.8883500191794669, \n                             'occupancy': 0.7142857142857143, \n                             'waiting_probability': 0.1741319335950498}\n```\n\nIf you want to run different scenarios at the same time, you can use the MultiErlangC, for example, trying different service levels:\n\n```python\nfrom pyworkforce.queuing import MultiErlangC\n\nparam_grid = {\"transactions\": [100], \"aht\": [3], \"interval\": [30], \"asa\": [20 / 60], \"shrinkage\": [0.3]}\nmulti_erlang = MultiErlangC(param_grid=param_grid, n_jobs=-1)\n\nrequired_positions_scenarios = {\"service_level\": [0.8, 0.85, 0.9], \"max_occupancy\": [0.8]}\n\npositions_requirements = multi_erlang.required_positions(required_positions_scenarios)\nprint(\"positions_requirements: \", positions_requirements)\n```\nOutput:\n```\n\u003e\u003e positions_requirements:   [\n                                {\n                                    \"raw_positions\": 13,\n                                    \"positions\": 19,\n                                    \"service_level\": 0.7955947884177831,\n                                    \"occupancy\": 0.7692307692307693,\n                                    \"waiting_probability\": 0.285270453036493\n                                },\n                                {\n                                    \"raw_positions\": 14,\n                                    \"positions\": 20,\n                                    \"service_level\": 0.8883500191794669,\n                                    \"occupancy\": 0.7142857142857143,\n                                    \"waiting_probability\": 0.1741319335950498\n                                },\n                                {\n                                    \"raw_positions\": 15,\n                                    \"positions\": 22,\n                                    \"service_level\": 0.9414528428690223,\n                                    \"occupancy\": 0.6666666666666666,\n                                    \"waiting_probability\": 0.10204236700798798\n                                }\n                            ]\n```\n### Scheduling\n\nA brief introduction can be found in this [medium post](https://towardsdatascience.com/how-to-solve-scheduling-problems-in-python-36a9af8de451)\n\n#### Example:\n\n```python\nfrom pyworkforce.scheduling import MinAbsDifference, MinRequiredResources\n\n# Rows are the days, each entry of a row, is number of positions required at an hour of the day (24). \nrequired_resources = [\n    [9, 11, 17, 9, 7, 12, 5, 11, 8, 9, 18, 17, 8, 12, 16, 8, 7, 12, 11, 10, 13, 19, 16, 7],\n    [13, 13, 12, 15, 18, 20, 13, 16, 17, 8, 13, 11, 6, 19, 11, 20, 19, 17, 10, 13, 14, 23, 16, 8]\n]\n\n# Each entry of a shift, an hour of the day (24), 1 if the shift covers that hour, 0 otherwise\nshifts_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],\n                   \"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],\n                   \"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],\n                   \"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]}\n\n# Method One\ndifference_scheduler = MinAbsDifference(num_days=2,\n                                        periods=24,\n                                        shifts_coverage=shifts_coverage,\n                                        required_resources=required_resources,\n                                        max_period_concurrency=27,\n                                        max_shift_concurrency=25)\n\ndifference_solution = difference_scheduler.solve()\n\n# Method Two\n\nrequirements_scheduler = MinRequiredResources(num_days=2,\n                                              periods=24,\n                                              shifts_coverage=shifts_coverage,\n                                              required_resources=required_resources,\n                                              max_period_concurrency=27,\n                                              max_shift_concurrency=25)\n\nrequirements_solution = requirements_scheduler.solve()\n\nprint(\"difference_solution :\", difference_solution)\n\nprint(\"requirements_solution :\", requirements_solution)\n```\nOutput:\n```\n\u003e\u003e difference_solution: {'status': 'OPTIMAL', \n                          'cost': 157.0, \n                          'resources_shifts': [{'day': 0, 'shift': 'Morning', 'resources': 8},\n                                               {'day': 0, 'shift': 'Afternoon', 'resources': 11},\n                                               {'day': 0, 'shift': 'Night', 'resources': 9}, \n                                               {'day': 0, 'shift': 'Mixed', 'resources': 1}, \n                                               {'day': 1, 'shift': 'Morning', 'resources': 13}, \n                                               {'day': 1, 'shift': 'Afternoon', 'resources': 17}, \n                                               {'day': 1, 'shift': 'Night', 'resources': 13}, \n                                               {'day': 1, 'shift': 'Mixed', 'resources': 0}]\n                          }\n\n\u003e\u003e requirements_solution: {'status': 'OPTIMAL', \n                           'cost': 113.0, \n                           'resources_shifts': [{'day': 0, 'shift': 'Morning', 'resources': 15}, \n                                                {'day': 0, 'shift': 'Afternoon', 'resources': 13}, \n                                                {'day': 0, 'shift': 'Night', 'resources': 19}, \n                                                {'day': 0, 'shift': 'Mixed', 'resources': 3}, \n                                                {'day': 1, 'shift': 'Morning', 'resources': 20}, \n                                                {'day': 1, 'shift': 'Afternoon', 'resources': 20}, \n                                                {'day': 1, 'shift': 'Night', 'resources': 23}, \n                                                {'day': 1, 'shift': 'Mixed', 'resources': 0}]}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frodrigo-arenas%2Fpyworkforce","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frodrigo-arenas%2Fpyworkforce","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frodrigo-arenas%2Fpyworkforce/lists"}