pyworkforce 0.5.0.dev0__tar.gz → 0.5.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. {pyworkforce-0.5.0.dev0 → pyworkforce-0.5.2}/PKG-INFO +75 -41
  2. {pyworkforce-0.5.0.dev0 → pyworkforce-0.5.2}/README.md +71 -53
  3. pyworkforce-0.5.2/pyworkforce/_version.py +1 -0
  4. {pyworkforce-0.5.0.dev0 → pyworkforce-0.5.2}/pyworkforce/queuing/erlang.py +81 -77
  5. {pyworkforce-0.5.0.dev0 → pyworkforce-0.5.2}/pyworkforce/queuing/tests/test_erlang.py +35 -5
  6. {pyworkforce-0.5.0.dev0 → pyworkforce-0.5.2}/pyworkforce/queuing/tests/test_multi_erlang.py +18 -9
  7. {pyworkforce-0.5.0.dev0 → pyworkforce-0.5.2}/pyworkforce/rostering/binary_programming.py +26 -25
  8. {pyworkforce-0.5.0.dev0 → pyworkforce-0.5.2}/pyworkforce/scheduling/base.py +15 -13
  9. {pyworkforce-0.5.0.dev0 → pyworkforce-0.5.2}/pyworkforce/scheduling/shifts_selection.py +30 -28
  10. {pyworkforce-0.5.0.dev0 → pyworkforce-0.5.2}/pyworkforce/utils/grid.py +1 -1
  11. {pyworkforce-0.5.0.dev0 → pyworkforce-0.5.2}/pyworkforce/utils/tests/test_parameter_grid.py +13 -6
  12. {pyworkforce-0.5.0.dev0 → pyworkforce-0.5.2}/pyworkforce.egg-info/PKG-INFO +75 -41
  13. pyworkforce-0.5.2/pyworkforce.egg-info/requires.txt +4 -0
  14. {pyworkforce-0.5.0.dev0 → pyworkforce-0.5.2}/setup.py +11 -11
  15. pyworkforce-0.5.0.dev0/pyworkforce/_version.py +0 -1
  16. pyworkforce-0.5.0.dev0/pyworkforce.egg-info/requires.txt +0 -4
  17. {pyworkforce-0.5.0.dev0 → pyworkforce-0.5.2}/LICENSE +0 -0
  18. {pyworkforce-0.5.0.dev0 → pyworkforce-0.5.2}/pyworkforce/__init__.py +0 -0
  19. {pyworkforce-0.5.0.dev0 → pyworkforce-0.5.2}/pyworkforce/queuing/__init__.py +0 -0
  20. {pyworkforce-0.5.0.dev0 → pyworkforce-0.5.2}/pyworkforce/queuing/tests/__init__.py +0 -0
  21. {pyworkforce-0.5.0.dev0 → pyworkforce-0.5.2}/pyworkforce/rostering/__init__.py +0 -0
  22. {pyworkforce-0.5.0.dev0 → pyworkforce-0.5.2}/pyworkforce/rostering/tests/__init__.py +0 -0
  23. {pyworkforce-0.5.0.dev0 → pyworkforce-0.5.2}/pyworkforce/rostering/tests/test_rostering.py +0 -0
  24. {pyworkforce-0.5.0.dev0 → pyworkforce-0.5.2}/pyworkforce/scheduling/__init__.py +0 -0
  25. {pyworkforce-0.5.0.dev0 → pyworkforce-0.5.2}/pyworkforce/scheduling/tests/__init__.py +0 -0
  26. {pyworkforce-0.5.0.dev0 → pyworkforce-0.5.2}/pyworkforce/scheduling/tests/test_shifts.py +0 -0
  27. {pyworkforce-0.5.0.dev0 → pyworkforce-0.5.2}/pyworkforce/scheduling/tests/test_utils.py +0 -0
  28. {pyworkforce-0.5.0.dev0 → pyworkforce-0.5.2}/pyworkforce/scheduling/utils.py +0 -0
  29. {pyworkforce-0.5.0.dev0 → pyworkforce-0.5.2}/pyworkforce/utils/__init__.py +0 -0
  30. {pyworkforce-0.5.0.dev0 → pyworkforce-0.5.2}/pyworkforce/utils/tests/__init__.py +0 -0
  31. {pyworkforce-0.5.0.dev0 → pyworkforce-0.5.2}/pyworkforce.egg-info/SOURCES.txt +0 -0
  32. {pyworkforce-0.5.0.dev0 → pyworkforce-0.5.2}/pyworkforce.egg-info/dependency_links.txt +0 -0
  33. {pyworkforce-0.5.0.dev0 → pyworkforce-0.5.2}/pyworkforce.egg-info/top_level.txt +0 -0
  34. {pyworkforce-0.5.0.dev0 → pyworkforce-0.5.2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: pyworkforce
3
- Version: 0.5.0.dev0
3
+ Version: 0.5.2
4
4
  Summary: Common tools for workforce management, schedule and optimization problems
5
5
  Home-page: https://github.com/rodrigo-arenas/pyworkforce
6
6
  Author: Rodrigo Arenas
@@ -11,69 +11,100 @@ Project-URL: Source Code, https://github.com/rodrigo-arenas/pyworkforce
11
11
  Project-URL: Bug Tracker, https://github.com/rodrigo-arenas/pyworkforce/issues
12
12
  Classifier: License :: OSI Approved :: MIT License
13
13
  Classifier: Programming Language :: Python :: 3
14
- Classifier: Programming Language :: Python :: 3.7
15
- Classifier: Programming Language :: Python :: 3.8
16
- Classifier: Programming Language :: Python :: 3.9
17
- Requires-Python: >=3.7
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Programming Language :: Python :: 3.14
17
+ Requires-Python: >=3.12,<3.15
18
18
  Description-Content-Type: text/markdown
19
19
  License-File: LICENSE
20
+ Requires-Dist: numpy>=1.26.0
21
+ Requires-Dist: ortools>=9.12.4544
22
+ Requires-Dist: pandas>=2.2.0
23
+ Requires-Dist: joblib>=1.4.0
24
+ Dynamic: author
25
+ Dynamic: author-email
26
+ Dynamic: classifier
27
+ Dynamic: description
28
+ Dynamic: description-content-type
29
+ Dynamic: home-page
30
+ Dynamic: license
31
+ Dynamic: license-file
32
+ Dynamic: project-url
33
+ Dynamic: requires-dist
34
+ Dynamic: requires-python
35
+ Dynamic: summary
20
36
 
21
37
 
22
38
  [![Build Status](https://www.travis-ci.com/rodrigo-arenas/pyworkforce.svg?branch=main)](https://www.travis-ci.com/rodrigo-arenas/pyworkforce)
23
39
  [![Codecov](https://codecov.io/gh/rodrigo-arenas/pyworkforce/branch/main/graphs/badge.svg?branch=main&service=github)](https://codecov.io/github/rodrigo-arenas/pyworkforce?branch=main)
24
40
  [![PyPI Version](https://badge.fury.io/py/pyworkforce.svg)](https://badge.fury.io/py/pyworkforce)
25
- [![Python Version](https://img.shields.io/badge/python-3.6%20%7C%203.7%20%7C%203.8%20%7C%203.9-blue)](https://www.python.org/downloads/)
41
+ [![Python Version](https://img.shields.io/badge/python-3.12%20%7C%203.13%20%7C%203.14-blue)](https://www.python.org/downloads/)
26
42
 
27
43
 
28
44
  # pyworkforce
29
- Standard tools for workforce management, queuing, scheduling, rostering and optimization problems.
45
+ Tools for workforce management problems such as queue staffing, shift scheduling,
46
+ rostering, and operations research optimization.
30
47
 
31
- Make sure to check the documentation, which is available [here](https://pyworkforce.readthedocs.io/en/stable/)
48
+ The full documentation is available at
49
+ [pyworkforce.readthedocs.io](https://pyworkforce.readthedocs.io/en/stable/).
32
50
 
33
- ## Features:
34
- pyworkforce currently includes:
51
+ ## Installation
52
+
53
+ We recommend installing pyworkforce in a virtual environment:
54
+
55
+ ```bash
56
+ pip install pyworkforce
57
+ ```
58
+
59
+ pyworkforce supports Python 3.12, 3.13, and 3.14.
60
+
61
+ If you are using Anaconda and run into installation issues, update the environment first:
62
+
63
+ ```bash
64
+ conda update --all
65
+ ```
66
+
67
+ If the issue is related to OR-Tools, check the
68
+ [OR-Tools installation guide](https://github.com/google/or-tools#installation).
69
+
70
+ For runnable examples, see the
71
+ [examples folder](https://github.com/rodrigo-arenas/pyworkforce/tree/develop/examples).
72
+
73
+ ## What pyworkforce Does
74
+
75
+ pyworkforce is organized around three planning steps:
35
76
 
36
77
  ### Queuing
37
- It solves the following system resource requirements:
78
+
79
+ Use `pyworkforce.queuing` when you need to estimate how many resources are required
80
+ to handle incoming work, for example calls arriving at a call center. The current
81
+ implementation uses Erlang C assumptions: constant arrival rate, infinite queue,
82
+ and no customer dropout.
38
83
 
39
84
  ![queue_system](https://raw.githubusercontent.com/rodrigo-arenas/pyworkforce/main/docs/images/erlangc_queue_system.png)
40
85
 
41
- - **queuing.ErlangC:** Find the number of resources required to attend incoming traffic to a constant rate,
42
- infinite queue length, and no dropout.
86
+ - **queuing.ErlangC:** Calculate staffing requirements and performance metrics for one queue scenario.
87
+ - **queuing.MultiErlangC:** Run multiple Erlang C scenarios from a parameter grid.
43
88
 
44
89
  ### Scheduling
45
90
 
46
- It finds the number of resources to schedule in a shift based on the number of required positions per time interval
47
- (found, for example, using [queuing.ErlangC](./pyworkforce/queuing/erlang.py)), maximum capacity restrictions and static shifts coverage.<br>
48
- - **scheduling.MinAbsDifference:** This module finds the "optimal" assignation by minimizing the total absolute
49
- differences between required resources per interval against the scheduled resources found by the solver.
50
- - **scheduling.MinRequiredResources**: This module finds the "optimal" assignation by minimizing the total
51
- weighted amount of scheduled resources (optionally weighted by shift cost), it ensures that in all intervals, there are
52
- never fewer resources shifted than the ones required per period.
53
-
54
- ### Rostering
55
-
56
- It assigns a list of resources to a list of required positions per day and shifts; it takes into account
57
- different restrictions as shift bans, consecutive shifts, resting days, and others.
58
- It also introduces soft restrictions like shift preferences.
59
-
60
- # Usage:
61
- Install pyworkforce
91
+ Use `pyworkforce.scheduling` when you already know the required resources by time
92
+ interval and need to choose how many people to place on each predefined shift.
62
93
 
63
- It's advised to install pyworkforce using a virtual env, inside the env use:
94
+ - **scheduling.MinAbsDifference:** Minimizes the total absolute difference between required and scheduled resources.
95
+ - **scheduling.MinRequiredResources:** Minimizes the total weighted number of scheduled resources while ensuring every interval is covered.
64
96
 
65
- ```
66
- pip install pyworkforce
67
- ```
97
+ ### Rostering
68
98
 
69
- If you are having troubles with or-tools installation, check the [or-tools guide](https://github.com/google/or-tools#installation)
99
+ Use `pyworkforce.rostering` when you have named resources and need to assign them
100
+ to days and shifts while respecting rules such as banned shifts, rest days,
101
+ minimum working hours, and preferences.
70
102
 
71
- For complete list and details of examples go to the
72
- [examples folder](https://github.com/rodrigo-arenas/pyworkforce/tree/develop/examples)
103
+ - **rostering.MinHoursRoster:** Builds a resource-level roster that covers shift requirements with the minimum scheduled hours.
73
104
 
74
105
  ### Queue systems:
75
106
 
76
- A brief introduction can be found in this [medium post](https://medium.com/mlearning-ai/workforce-planning-optimization-using-python-69af0ef9011a)
107
+ A brief introduction can be found in this [medium post](https://towardsdatascience.com/workforce-planning-optimization-using-python-69af0ef9011a)
77
108
 
78
109
  #### Example:
79
110
 
@@ -94,7 +125,8 @@ Output:
94
125
  'waiting_probability': 0.1741319335950498}
95
126
  ```
96
127
 
97
- If you want to run different scenarios at the same time, you can use the MultiErlangC, for example, trying different service levels:
128
+ If you want to run several scenarios at the same time, use `MultiErlangC`.
129
+ For example, this tries different service-level targets:
98
130
 
99
131
  ```python
100
132
  from pyworkforce.queuing import MultiErlangC
@@ -135,18 +167,20 @@ Output:
135
167
  ```
136
168
  ### Scheduling
137
169
 
170
+ A brief introduction can be found in this [medium post](https://towardsdatascience.com/how-to-solve-scheduling-problems-in-python-36a9af8de451)
171
+
138
172
  #### Example:
139
173
 
140
174
  ```python
141
175
  from pyworkforce.scheduling import MinAbsDifference, MinRequiredResources
142
176
 
143
- # Rows are the days, each entry of a row, is number of positions required at an hour of the day (24).
177
+ # Rows are days. Each value is the number of required positions for one hour of the day.
144
178
  required_resources = [
145
179
  [9, 11, 17, 9, 7, 12, 5, 11, 8, 9, 18, 17, 8, 12, 16, 8, 7, 12, 11, 10, 13, 19, 16, 7],
146
180
  [13, 13, 12, 15, 18, 20, 13, 16, 17, 8, 13, 11, 6, 19, 11, 20, 19, 17, 10, 13, 14, 23, 16, 8]
147
181
  ]
148
182
 
149
- # Each entry of a shift,an hour of the day (24), 1 if the shift covers that hour, 0 otherwise
183
+ # Each shift has 24 entries, one per hour. Use 1 if the shift covers that hour, otherwise 0.
150
184
  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],
151
185
  "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],
152
186
  "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],
@@ -2,58 +2,73 @@
2
2
  [![Build Status](https://www.travis-ci.com/rodrigo-arenas/pyworkforce.svg?branch=main)](https://www.travis-ci.com/rodrigo-arenas/pyworkforce)
3
3
  [![Codecov](https://codecov.io/gh/rodrigo-arenas/pyworkforce/branch/main/graphs/badge.svg?branch=main&service=github)](https://codecov.io/github/rodrigo-arenas/pyworkforce?branch=main)
4
4
  [![PyPI Version](https://badge.fury.io/py/pyworkforce.svg)](https://badge.fury.io/py/pyworkforce)
5
- [![Python Version](https://img.shields.io/badge/python-3.6%20%7C%203.7%20%7C%203.8%20%7C%203.9-blue)](https://www.python.org/downloads/)
6
-
7
-
8
- # pyworkforce
9
- Standard tools for workforce management, queuing, scheduling, rostering and optimization problems.
10
-
11
- Make sure to check the documentation, which is available [here](https://pyworkforce.readthedocs.io/en/stable/)
12
-
13
- ## Features:
14
- pyworkforce currently includes:
15
-
16
- ### Queuing
17
- It solves the following system resource requirements:
18
-
19
- ![queue_system](https://raw.githubusercontent.com/rodrigo-arenas/pyworkforce/main/docs/images/erlangc_queue_system.png)
20
-
21
- - **queuing.ErlangC:** Find the number of resources required to attend incoming traffic to a constant rate,
22
- infinite queue length, and no dropout.
23
-
24
- ### Scheduling
25
-
26
- It finds the number of resources to schedule in a shift based on the number of required positions per time interval
27
- (found, for example, using [queuing.ErlangC](./pyworkforce/queuing/erlang.py)), maximum capacity restrictions and static shifts coverage.<br>
28
- - **scheduling.MinAbsDifference:** This module finds the "optimal" assignation by minimizing the total absolute
29
- differences between required resources per interval against the scheduled resources found by the solver.
30
- - **scheduling.MinRequiredResources**: This module finds the "optimal" assignation by minimizing the total
31
- weighted amount of scheduled resources (optionally weighted by shift cost), it ensures that in all intervals, there are
32
- never fewer resources shifted than the ones required per period.
33
-
34
- ### Rostering
35
-
36
- It assigns a list of resources to a list of required positions per day and shifts; it takes into account
37
- different restrictions as shift bans, consecutive shifts, resting days, and others.
38
- It also introduces soft restrictions like shift preferences.
39
-
40
- # Usage:
41
- Install pyworkforce
42
-
43
- It's advised to install pyworkforce using a virtual env, inside the env use:
44
-
45
- ```
46
- pip install pyworkforce
47
- ```
48
-
49
- If you are having troubles with or-tools installation, check the [or-tools guide](https://github.com/google/or-tools#installation)
50
-
51
- For complete list and details of examples go to the
52
- [examples folder](https://github.com/rodrigo-arenas/pyworkforce/tree/develop/examples)
5
+ [![Python Version](https://img.shields.io/badge/python-3.12%20%7C%203.13%20%7C%203.14-blue)](https://www.python.org/downloads/)
6
+
7
+
8
+ # pyworkforce
9
+ Tools for workforce management problems such as queue staffing, shift scheduling,
10
+ rostering, and operations research optimization.
11
+
12
+ The full documentation is available at
13
+ [pyworkforce.readthedocs.io](https://pyworkforce.readthedocs.io/en/stable/).
14
+
15
+ ## Installation
16
+
17
+ We recommend installing pyworkforce in a virtual environment:
18
+
19
+ ```bash
20
+ pip install pyworkforce
21
+ ```
22
+
23
+ pyworkforce supports Python 3.12, 3.13, and 3.14.
24
+
25
+ If you are using Anaconda and run into installation issues, update the environment first:
26
+
27
+ ```bash
28
+ conda update --all
29
+ ```
30
+
31
+ If the issue is related to OR-Tools, check the
32
+ [OR-Tools installation guide](https://github.com/google/or-tools#installation).
33
+
34
+ For runnable examples, see the
35
+ [examples folder](https://github.com/rodrigo-arenas/pyworkforce/tree/develop/examples).
36
+
37
+ ## What pyworkforce Does
38
+
39
+ pyworkforce is organized around three planning steps:
40
+
41
+ ### Queuing
42
+
43
+ Use `pyworkforce.queuing` when you need to estimate how many resources are required
44
+ to handle incoming work, for example calls arriving at a call center. The current
45
+ implementation uses Erlang C assumptions: constant arrival rate, infinite queue,
46
+ and no customer dropout.
47
+
48
+ ![queue_system](https://raw.githubusercontent.com/rodrigo-arenas/pyworkforce/main/docs/images/erlangc_queue_system.png)
49
+
50
+ - **queuing.ErlangC:** Calculate staffing requirements and performance metrics for one queue scenario.
51
+ - **queuing.MultiErlangC:** Run multiple Erlang C scenarios from a parameter grid.
52
+
53
+ ### Scheduling
54
+
55
+ Use `pyworkforce.scheduling` when you already know the required resources by time
56
+ interval and need to choose how many people to place on each predefined shift.
57
+
58
+ - **scheduling.MinAbsDifference:** Minimizes the total absolute difference between required and scheduled resources.
59
+ - **scheduling.MinRequiredResources:** Minimizes the total weighted number of scheduled resources while ensuring every interval is covered.
60
+
61
+ ### Rostering
62
+
63
+ Use `pyworkforce.rostering` when you have named resources and need to assign them
64
+ to days and shifts while respecting rules such as banned shifts, rest days,
65
+ minimum working hours, and preferences.
66
+
67
+ - **rostering.MinHoursRoster:** Builds a resource-level roster that covers shift requirements with the minimum scheduled hours.
53
68
 
54
69
  ### Queue systems:
55
70
 
56
- A brief introduction can be found in this [medium post](https://medium.com/mlearning-ai/workforce-planning-optimization-using-python-69af0ef9011a)
71
+ A brief introduction can be found in this [medium post](https://towardsdatascience.com/workforce-planning-optimization-using-python-69af0ef9011a)
57
72
 
58
73
  #### Example:
59
74
 
@@ -74,7 +89,8 @@ Output:
74
89
  'waiting_probability': 0.1741319335950498}
75
90
  ```
76
91
 
77
- If you want to run different scenarios at the same time, you can use the MultiErlangC, for example, trying different service levels:
92
+ If you want to run several scenarios at the same time, use `MultiErlangC`.
93
+ For example, this tries different service-level targets:
78
94
 
79
95
  ```python
80
96
  from pyworkforce.queuing import MultiErlangC
@@ -115,18 +131,20 @@ Output:
115
131
  ```
116
132
  ### Scheduling
117
133
 
134
+ A brief introduction can be found in this [medium post](https://towardsdatascience.com/how-to-solve-scheduling-problems-in-python-36a9af8de451)
135
+
118
136
  #### Example:
119
137
 
120
138
  ```python
121
139
  from pyworkforce.scheduling import MinAbsDifference, MinRequiredResources
122
140
 
123
- # Rows are the days, each entry of a row, is number of positions required at an hour of the day (24).
141
+ # Rows are days. Each value is the number of required positions for one hour of the day.
124
142
  required_resources = [
125
143
  [9, 11, 17, 9, 7, 12, 5, 11, 8, 9, 18, 17, 8, 12, 16, 8, 7, 12, 11, 10, 13, 19, 16, 7],
126
144
  [13, 13, 12, 15, 18, 20, 13, 16, 17, 8, 13, 11, 6, 19, 11, 20, 19, 17, 10, 13, 14, 23, 16, 8]
127
145
  ]
128
146
 
129
- # Each entry of a shift,an hour of the day (24), 1 if the shift covers that hour, 0 otherwise
147
+ # Each shift has 24 entries, one per hour. Use 1 if the shift covers that hour, otherwise 0.
130
148
  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],
131
149
  "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],
132
150
  "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],
@@ -181,4 +199,4 @@ Output:
181
199
  {'day': 1, 'shift': 'Afternoon', 'resources': 20},
182
200
  {'day': 1, 'shift': 'Night', 'resources': 23},
183
201
  {'day': 1, 'shift': 'Mixed', 'resources': 0}]}
184
- ```
202
+ ```
@@ -0,0 +1 @@
1
+ __version__ = "0.5.2"
@@ -5,22 +5,22 @@ from joblib import Parallel, delayed
5
5
 
6
6
  class ErlangC:
7
7
  """
8
- Computes the number of positions required to attend a number of transactions in a
9
- queue system based on erlangc.rst. Implementation inspired on:
8
+ Computes the number of positions required to handle transactions in an
9
+ Erlang C queue system. Implementation inspired by:
10
10
  https://lucidmanager.org/data-science/call-centre-workforce-planning-erlang-c-in-r/
11
11
 
12
12
  Parameters
13
13
  ----------
14
14
  transactions: float,
15
- The number of total transactions that comes in an interval.
15
+ Total number of transactions arriving in the interval.
16
16
  aht: float,
17
17
  Average handling time of a transaction (minutes).
18
18
  asa: float,
19
19
  The required average speed of answer (minutes).
20
20
  interval: int,
21
- Interval length (minutes) where the transactions come in
21
+ Interval length, in minutes.
22
22
  shrinkage: float,
23
- Percentage of time that an operator unit is not available.
23
+ Fraction of time that an operator unit is not available.
24
24
  """
25
25
 
26
26
  def __init__(self, transactions: float, aht: float, asa: float,
@@ -46,51 +46,61 @@ class ErlangC:
46
46
  self.aht = aht
47
47
  self.interval = interval
48
48
  self.asa = asa
49
- self.intensity = (self.n_transactions / self.interval) * self.aht
50
- self.shrinkage = shrinkage
51
-
52
- def waiting_probability(self, positions: int, scale_positions: bool = False):
49
+ self.intensity = (self.n_transactions / self.interval) * self.aht
50
+ self.shrinkage = shrinkage
51
+
52
+ def _productive_positions(self, positions: int, scale_positions: bool = False):
53
+ if scale_positions:
54
+ productive_positions = floor((1 - self.shrinkage) * positions)
55
+ else:
56
+ productive_positions = positions
57
+
58
+ if productive_positions <= 0:
59
+ raise ValueError("productive positions must be greater than 0")
60
+
61
+ if productive_positions <= self.intensity:
62
+ raise ValueError("positions must be greater than traffic intensity")
63
+
64
+ return productive_positions
65
+
66
+ def waiting_probability(self, positions: int, scale_positions: bool = False):
53
67
  """
54
- Returns the probability of waiting in the queue
68
+ Returns the probability that a transaction waits in queue.
55
69
 
56
70
  Parameters
57
71
  ----------
58
- positions: int,
59
- The number of positions to attend the transactions.
60
- scale_positions: bool, default=False
61
- Set it to True if the positions were calculated using shrinkage.
72
+ positions: int,
73
+ The number of positions available to handle transactions.
74
+ Productive positions must be greater than traffic intensity.
75
+ scale_positions: bool, default=False
76
+ Set to True when ``positions`` includes shrinkage.
62
77
 
63
78
  """
64
79
 
65
- if scale_positions:
66
- productive_positions = floor((1 - self.shrinkage) * positions)
67
- else:
68
- productive_positions = positions
69
-
70
- erlang_b_inverse = 1
71
- for position in range(1, productive_positions + 1):
72
- erlang_b_inverse = 1 + (erlang_b_inverse * position / self.intensity)
80
+ productive_positions = self._productive_positions(positions, scale_positions)
81
+
82
+ erlang_b_inverse = 1
83
+ for position in range(1, productive_positions + 1):
84
+ erlang_b_inverse = 1 + (erlang_b_inverse * position / self.intensity)
73
85
 
74
86
  erlang_b = 1 / erlang_b_inverse
75
87
  return productive_positions * erlang_b / (productive_positions - self.intensity * (1 - erlang_b))
76
88
 
77
89
  def service_level(self, positions: int, scale_positions: bool = False):
78
90
  """
79
- Returns the expected service level given a number of positions
91
+ Returns the expected service level for a number of positions.
80
92
 
81
93
  Parameters
82
94
  ----------
83
95
 
84
- positions: int,
85
- The number of positions attending.
86
- scale_positions: bool, default = False
87
- Set it to True if the positions were calculated using shrinkage.
96
+ positions: int,
97
+ The number of positions available to handle transactions.
98
+ Productive positions must be greater than traffic intensity.
99
+ scale_positions: bool, default = False
100
+ Set to True when ``positions`` includes shrinkage.
88
101
 
89
102
  """
90
- if scale_positions:
91
- productive_positions = floor((1 - self.shrinkage) * positions)
92
- else:
93
- productive_positions = positions
103
+ productive_positions = self._productive_positions(positions, scale_positions)
94
104
 
95
105
  probability_wait = self.waiting_probability(productive_positions, scale_positions=False)
96
106
  exponential = exp(-(productive_positions - self.intensity) * (self.asa / self.aht))
@@ -98,57 +108,58 @@ class ErlangC:
98
108
 
99
109
  def achieved_occupancy(self, positions: int, scale_positions: bool = False):
100
110
  """
101
- Returns the expected occupancy of positions
111
+ Returns the expected occupancy of positions.
102
112
 
103
113
  Parameters
104
114
  ----------
105
115
 
106
- positions: int,
107
- The number of raw positions
108
- scale_positions: bool, default=False
109
- Set it to True if the positions were calculated using shrinkage.
116
+ positions: int,
117
+ The number of raw positions.
118
+ Productive positions must be greater than traffic intensity.
119
+ scale_positions: bool, default=False
120
+ Set to True when ``positions`` includes shrinkage.
110
121
 
111
122
  """
112
- if scale_positions:
113
- productive_positions = floor((1 - self.shrinkage) * positions)
114
- else:
115
- productive_positions = positions
123
+ productive_positions = self._productive_positions(positions, scale_positions)
116
124
 
117
125
  return self.intensity / productive_positions
118
126
 
119
127
  def required_positions(self, service_level: float, max_occupancy: float = 1.0):
120
128
  """
121
- Computes the requirements using erlangc.rst
129
+ Computes the required positions for a target service level.
122
130
 
123
131
  Parameters
124
132
  ----------
125
133
 
126
134
  service_level: float,
127
- Target service level
128
- max_occupancy: float,
129
- The maximum fraction of time that a transaction can occupy a position
135
+ Target service level.
136
+ max_occupancy: float,
137
+ The maximum fraction of time that a transaction can occupy a position.
138
+ Must be greater than 0 and less than or equal to 1.
130
139
 
131
140
  Returns
132
141
  -------
133
142
 
134
143
  raw_positions: int,
135
- The required positions assuming shrinkage = 0
144
+ Required positions before applying shrinkage.
136
145
  positions: int,
137
- The number of positions needed to ensure the required service level
146
+ Positions needed after applying shrinkage.
138
147
  service_level: float,
139
- The fraction of transactions that are expected to be assigned to a position,
140
- before the asa time
148
+ Fraction of transactions expected to reach a position before the target ASA.
141
149
  occupancy: float,
142
- The expected occupancy of positions
150
+ Expected occupancy of positions.
143
151
  waiting_probability: float,
144
- The probability of a transaction waiting in the queue
152
+ Probability that a transaction waits in queue.
145
153
  """
146
154
 
147
155
  if service_level < 0 or service_level > 1:
148
156
  raise ValueError("service_level must be between 0 and 1")
149
157
 
150
- if max_occupancy < 0 or max_occupancy > 1:
151
- raise ValueError("max_occupancy must be between 0 and 1")
158
+ if max_occupancy < 0 or max_occupancy > 1:
159
+ raise ValueError("max_occupancy must be between 0 and 1")
160
+
161
+ if max_occupancy == 0:
162
+ raise ValueError("max_occupancy must be greater than 0")
152
163
 
153
164
  positions = round(self.intensity + 1)
154
165
  achieved_service_level = self.service_level(positions, scale_positions=False)
@@ -177,47 +188,40 @@ class ErlangC:
177
188
 
178
189
  class MultiErlangC:
179
190
  """
180
- This class uses the erlangc.rst class using joblib's Parallel,
181
- allowing to run multiple scenarios at once.
182
- It finds solutions iterating over all possible combinations provided by the users,
183
- inspired how Sklearn's Grid Search works
191
+ Runs Erlang C calculations over multiple parameter combinations.
192
+
193
+ This class uses joblib's ``Parallel`` to evaluate every combination from
194
+ ``param_grid`` and the method-specific argument grid. Its interface is
195
+ inspired by scikit-learn's grid search utilities.
184
196
 
185
197
  Parameters
186
198
  ----------
187
199
 
188
200
  param_grid: dict,
189
- Dictionary with the erlangc.rst.__init__ parameters, each key of the dictionary must be the
190
- expected parameter and the value must be a list with the different options to iterate
201
+ Dictionary with :class:`ErlangC` initialization parameters. Each key must be an
202
+ expected parameter, and each value must be a list of options to iterate over.
191
203
  example: {"transactions": [100, 200], "aht": [3], "interval": [30], "asa": [20 / 60], "shrinkage": [0.3]}
192
204
  n_jobs: int, default=2
193
- The maximum number of concurrently running jobs.
205
+ Maximum number of concurrently running jobs.
194
206
  If -1 all CPUs are used. If 1 is given, no parallel computing code is used at all, which is useful for debugging.
195
207
  For n_jobs below -1, (n_cpus + 1 + n_jobs) are used. Thus for n_jobs = -2, all CPUs but one are used.
196
208
  None is a marker for ‘unset’ that will be interpreted as n_jobs=1 (sequential execution)
197
209
  unless the call is performed under a parallel_backend() context manager that sets another value for n_jobs.
198
210
  pre_dispatch: {"all", int, or expression}, default='2 * n_jobs'
199
- The number of batches (of tasks) to be pre-dispatched. Default is 2*n_jobs’.
211
+ Number of task batches to pre-dispatch. Default is ``2*n_jobs``.
200
212
  See joblib's documentation for more details: https://joblib.readthedocs.io/en/latest/generated/joblib.Parallel.html
201
213
 
202
214
  Attributes
203
215
  ----------
204
216
 
205
217
  waiting_probability_params: list[tuple],
206
- Each tuple of the list represents the used parameters in param_grid for ErlangC and
207
- arguments_grid for waiting_probability method,corresponding to the same order returned
208
- by the MultiErlangC.waiting_probability method.
218
+ Parameters used for each ``waiting_probability`` result, in result order.
209
219
  service_level_params: list[tuple],
210
- Each tuple of the list represents the used parameters in param_grid for ErlangC and
211
- arguments_grid for service_level method,corresponding to the same order returned
212
- by the MultiErlangC.service_level method.
220
+ Parameters used for each ``service_level`` result, in result order.
213
221
  achieved_occupancy_params: list[tuple],
214
- Each tuple of the list represents the used parameters in param_grid for ErlangC and
215
- arguments_grid for achieved_occupancy method,corresponding to the same order returned
216
- by the MultiErlangC.achieved_occupancy method.
222
+ Parameters used for each ``achieved_occupancy`` result, in result order.
217
223
  required_positions_params: list[tuple],
218
- Each tuple of the list represents the used parameters in param_grid for ErlangC and
219
- arguments_grid for required_positions method,corresponding to the same order returned
220
- by the MultiErlangC.required_positions method.
224
+ Parameters used for each ``required_positions`` result, in result order.
221
225
  """
222
226
 
223
227
  def __init__(self, param_grid: dict, n_jobs: int = 2, pre_dispatch: str = '2 * n_jobs'):
@@ -347,8 +351,8 @@ class MultiErlangC:
347
351
  if len(solutions) < 1: # noqa
348
352
  raise ValueError("Could not find any solution, make sure the param_grid is defined correctly")
349
353
 
350
- if len(solutions) != combinations:
351
- raise ValueError('Inconsistent results. Expected {} '
352
- 'solutions, got {}'
353
- .format(len(self.param_list),
354
- len(solutions))) # noqa
354
+ if len(solutions) != combinations:
355
+ raise ValueError('Inconsistent results. Expected {} '
356
+ 'solutions, got {}'
357
+ .format(combinations,
358
+ len(solutions))) # noqa