leaguescheduler 0.1.0__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.
@@ -0,0 +1,7 @@
1
+ Copyright 2026 Samuel Borms
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,210 @@
1
+ Metadata-Version: 2.4
2
+ Name: leaguescheduler
3
+ Version: 0.1.0
4
+ Summary: Generate optimal schedules for your time-relaxed double round-robin (2RR) sports leagues
5
+ Author-email: Samuel Borms <sam@desirdata.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/sborms/leaguescheduler
8
+ Project-URL: Repository, https://github.com/sborms/leaguescheduler
9
+ Project-URL: Issues, https://github.com/sborms/leaguescheduler/issues
10
+ Keywords: scheduling,sports,league,round-robin,tabu-search
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: End Users/Desktop
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Topic :: Scientific/Engineering
17
+ Requires-Python: <3.14,>=3.13
18
+ Description-Content-Type: text/markdown
19
+ License-File: LICENSE
20
+ Requires-Dist: matplotlib>=3.9.0
21
+ Requires-Dist: numpy>=2.3.4
22
+ Requires-Dist: openpyxl>=3.1.3
23
+ Requires-Dist: pandas>=2.3.0
24
+ Requires-Dist: typer>=0.15.2
25
+ Requires-Dist: XlsxWriter>=3.2.0
26
+ Requires-Dist: fasttps>=0.1.0
27
+ Requires-Dist: tool>=0.8.0
28
+ Requires-Dist: ty>=0.0.19
29
+ Provides-Extra: app
30
+ Requires-Dist: streamlit>=1.54.0; extra == "app"
31
+ Dynamic: license-file
32
+
33
+ # ⚽📅 2RR League Scheduler
34
+
35
+ [![PyPI](https://img.shields.io/pypi/v/leaguescheduler)](https://pypi.org/project/leaguescheduler/)
36
+ [![Python](https://img.shields.io/pypi/pyversions/leaguescheduler)](https://pypi.org/project/leaguescheduler/)
37
+ [![CI](https://github.com/sborms/leaguescheduler/actions/workflows/ci.yaml/badge.svg)](https://github.com/sborms/leaguescheduler/actions/workflows/ci.yaml)
38
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
39
+ [![Streamlit App](https://static.streamlit.io/badges/streamlit_badge_black_white.svg)](https://leaguescheduler.streamlit.app)
40
+
41
+ If you are looking to schedule a sports league with at least the following constraints...
42
+
43
+ - Everyone plays 1 home game and 1 away game against each other (double round-robin)
44
+ - Home games are played on reserved dates
45
+ - Away games are not played on unavailable dates
46
+ - No team plays 2 games on the same day
47
+ - The calendar is spread out such that teams have enough rest days between games
48
+
49
+ ... then this will help you!
50
+
51
+ This software implements **constrained time-relaxed double round-robin (2RR) sports league scheduling** using the tabu search based heuristic algorithm described in the paper [**Scheduling a non-professional indoor football league**](https://pure.tue.nl/ws/portalfiles/portal/121797609/Bulck2019_Article_SchedulingANon_professionalInd.pdf) by Van Bulck, Goosens and Spieksma (2019). The meta-algorithm heavily relies on the Hungarian algorithm to recurrently solve the transportation problem. Some additional tricks were added, especially to minimize excessive rest days (internally fixed at 28) between consecutive games of teams.
52
+
53
+ ## Installation
54
+
55
+ Install from PyPI:
56
+
57
+ ```bash
58
+ pip install leaguescheduler
59
+ ```
60
+
61
+ Or with [uv](https://docs.astral.sh/uv):
62
+
63
+ ```bash
64
+ uv add leaguescheduler
65
+ ```
66
+
67
+ For development, clone the repository and run:
68
+
69
+ ```bash
70
+ uv venv
71
+ uv sync
72
+ ```
73
+
74
+ ## Usage
75
+
76
+ ### Input
77
+
78
+ The Excel file `example/input.xlsx` contains an example of the input data. The input should always, in the exact format as in the example, include the reserved dates (together with location and time) and unavailable dates of all teams from a single league.
79
+
80
+ One sheet corresponds to one league.
81
+
82
+ For instance, a league could consist of 10 teams, each with about 12 to 20 reserved dates and a number of unavailable dates.
83
+
84
+ ### Output
85
+
86
+ The generated output for a single league is a solutions matrix `X` that can easily be converted into a clear `DataFrame` calendar, and stored as an Excel file.
87
+
88
+ The calendar includes the date, time, location, home team and away team for each game. The unplanned games are put at the bottom.
89
+
90
+ ### Scheduling
91
+
92
+ #### CLI
93
+
94
+ You can use the scheduler from the command line as follows:
95
+
96
+ ```bash
97
+ 2rr \
98
+ --input_file "example/input.xlsx" \
99
+ --output_folder "example/output" \
100
+ --seed 321 \
101
+ --n_iterations 500
102
+ ```
103
+
104
+ Alternatively, you can execute `make example` which runs the above example.
105
+
106
+ See `2rr --help` (and the research paper mentioned at the top) for more information about all the available arguments.
107
+
108
+ <details>
109
+ <summary><b>CLI reference</b></summary>
110
+
111
+ | Option | Type | Default | Description |
112
+ |--------|------|---------|-------------|
113
+ | `--input-file` | text | *required* | Input Excel file with for every team their (un)availability data |
114
+ | `--output-folder` | text | *required* | Folder where the outputs (logs, overview, schedules) will be stored |
115
+ | `--seed` | integer | `None` | Optional seed for `np.random.seed()` |
116
+ | `--unavailable` | text | `NIET` | Cell value to indicate that a team is unavailable |
117
+ | `--clip-bot` | integer | `1` | Value for clipping rest days plot on low end |
118
+ | `--clip-upp` | integer | `41` | Value for clipping rest days plot on high end |
119
+ | `--net / --no-net` | flag | `--no-net` | Report the adjusted number of rest days |
120
+ | `--tabu-length` | integer | `4` | Number of iterations during which a team cannot be selected |
121
+ | `--perturbation-length` | integer | `50` | Check perturbation need every this many iterations |
122
+ | `--n-iterations` | integer | `10000` | Number of tabu phase iterations |
123
+ | `--m` | integer | `7` | Minimum number of time slots between 2 games with same pair of teams |
124
+ | `--p` | integer | `1000` | Cost from dummy supply node q to non-dummy demand node |
125
+ | `--r-max` | integer | `4` | Minimum required time slots for 2 games of same team |
126
+ | `--alpha` | float | `0.5` | Probability of picking perturbation operator 1 |
127
+ | `--beta` | float | `0.01` | Probability of removing a game in operator 1 |
128
+ | `--cost-excessive-rest-days` | float | `500` | Cost for excessive rest days |
129
+
130
+ </details>
131
+
132
+ #### Classes
133
+
134
+ To more freely play around, you can import the core classes in your own Python script or notebook.
135
+
136
+ Here's a minimal example with default parameters:
137
+
138
+ ```python
139
+ from leaguescheduler import InputParser, LeagueScheduler
140
+
141
+ input = InputParser(input_file)
142
+ input.from_excel(sheet_name=input.sheet_names[0])
143
+ input.parse()
144
+
145
+ scheduler = LeagueScheduler(input=input)
146
+ scheduler.construction_phase()
147
+ scheduler.tabu_phase()
148
+
149
+ df = scheduler.create_calendar()
150
+ scheduler.store_calendar(df, file="out/calendar.xlsx")
151
+ ```
152
+
153
+ Type `help(LeagueScheduler)` to show the full documentation.
154
+
155
+ <details>
156
+ <summary><b>Python API reference</b></summary>
157
+
158
+ **`InputParser(filename, unavailable="NIET")`** — Reads and parses input Excel file.
159
+
160
+ **`SchedulerParams(...)`** — Configuration dataclass for the scheduler.
161
+
162
+ | Parameter | Type | Default | Description |
163
+ |-----------|------|---------|-------------|
164
+ | `tabu_length` | int | `4` | Iterations during which a team cannot be selected |
165
+ | `perturbation_length` | int | `50` | Check perturbation need every this many iterations |
166
+ | `n_iterations` | int | `10000` | Number of tabu phase iterations |
167
+ | `m` | int | `7` | Min time slots between 2 games with same pair |
168
+ | `p` | int | `1000` | Cost from dummy supply node to non-dummy demand node |
169
+ | `r_max` | int | `4` | Min required time slots for 2 games of same team |
170
+ | `penalties` | dict | `{}` | Custom penalty mapping for rest days |
171
+ | `alpha` | float | `0.5` | Probability of picking perturbation operator 1 |
172
+ | `beta` | float | `0.01` | Probability of removing a game in operator 1 |
173
+ | `cost_excessive_rest_days` | float | `500` | Cost for excessive rest days |
174
+
175
+ **`LeagueScheduler(input, params=SchedulerParams(), logger=None)`** — Main scheduler class.
176
+
177
+ </details>
178
+
179
+ #### Web application
180
+
181
+ The league scheduler is also made available through a hosted [Streamlit application](https://leaguescheduler.streamlit.app).
182
+
183
+ It has a more limited set of parameters (namely `m`, `r_max`, `n_iterations`, and `penalties`) but can be used out of the box yet without logging.
184
+
185
+ Additionally, the output file includes for every league and by team the distribution of the **number of *adjusted* rest days between games** (meaning that unavailable dates by that team are not considered in the count of the rest days), as well as the **unused home time slots per team**. This facilitates post-analysis of the quality of the generated calendar.
186
+
187
+ If the app sleeps due to inactivity 😴, just wake it back up. You can run the app locally with `make web`.
188
+
189
+ #### Timings
190
+
191
+ How long does the scheduler take? This table sheds some baseline light for a league of **13 teams**:
192
+
193
+ | Iterations | Time |
194
+ |------------------|----------- |
195
+ | 10 | <1s |
196
+ | 100 | <1s |
197
+ | 1k | <1s |
198
+ | 10k | ~3s |
199
+ | 100k | ~25s |
200
+ | 1M | ~245s |
201
+
202
+ *Run on a few years old Windows 10 Pro machine with Intel i7–7700HQ CPU and 32GB RAM.*
203
+
204
+ A few 100(0)s iterations are typically sufficient to arrive at a good schedule.
205
+
206
+ Quite fast. Thanks to Ra-Ra-Rust! 🦀
207
+
208
+ ## Feedback?
209
+
210
+ Let me know if you have any feedback or suggestions for improvement!
@@ -0,0 +1,178 @@
1
+ # ⚽📅 2RR League Scheduler
2
+
3
+ [![PyPI](https://img.shields.io/pypi/v/leaguescheduler)](https://pypi.org/project/leaguescheduler/)
4
+ [![Python](https://img.shields.io/pypi/pyversions/leaguescheduler)](https://pypi.org/project/leaguescheduler/)
5
+ [![CI](https://github.com/sborms/leaguescheduler/actions/workflows/ci.yaml/badge.svg)](https://github.com/sborms/leaguescheduler/actions/workflows/ci.yaml)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
+ [![Streamlit App](https://static.streamlit.io/badges/streamlit_badge_black_white.svg)](https://leaguescheduler.streamlit.app)
8
+
9
+ If you are looking to schedule a sports league with at least the following constraints...
10
+
11
+ - Everyone plays 1 home game and 1 away game against each other (double round-robin)
12
+ - Home games are played on reserved dates
13
+ - Away games are not played on unavailable dates
14
+ - No team plays 2 games on the same day
15
+ - The calendar is spread out such that teams have enough rest days between games
16
+
17
+ ... then this will help you!
18
+
19
+ This software implements **constrained time-relaxed double round-robin (2RR) sports league scheduling** using the tabu search based heuristic algorithm described in the paper [**Scheduling a non-professional indoor football league**](https://pure.tue.nl/ws/portalfiles/portal/121797609/Bulck2019_Article_SchedulingANon_professionalInd.pdf) by Van Bulck, Goosens and Spieksma (2019). The meta-algorithm heavily relies on the Hungarian algorithm to recurrently solve the transportation problem. Some additional tricks were added, especially to minimize excessive rest days (internally fixed at 28) between consecutive games of teams.
20
+
21
+ ## Installation
22
+
23
+ Install from PyPI:
24
+
25
+ ```bash
26
+ pip install leaguescheduler
27
+ ```
28
+
29
+ Or with [uv](https://docs.astral.sh/uv):
30
+
31
+ ```bash
32
+ uv add leaguescheduler
33
+ ```
34
+
35
+ For development, clone the repository and run:
36
+
37
+ ```bash
38
+ uv venv
39
+ uv sync
40
+ ```
41
+
42
+ ## Usage
43
+
44
+ ### Input
45
+
46
+ The Excel file `example/input.xlsx` contains an example of the input data. The input should always, in the exact format as in the example, include the reserved dates (together with location and time) and unavailable dates of all teams from a single league.
47
+
48
+ One sheet corresponds to one league.
49
+
50
+ For instance, a league could consist of 10 teams, each with about 12 to 20 reserved dates and a number of unavailable dates.
51
+
52
+ ### Output
53
+
54
+ The generated output for a single league is a solutions matrix `X` that can easily be converted into a clear `DataFrame` calendar, and stored as an Excel file.
55
+
56
+ The calendar includes the date, time, location, home team and away team for each game. The unplanned games are put at the bottom.
57
+
58
+ ### Scheduling
59
+
60
+ #### CLI
61
+
62
+ You can use the scheduler from the command line as follows:
63
+
64
+ ```bash
65
+ 2rr \
66
+ --input_file "example/input.xlsx" \
67
+ --output_folder "example/output" \
68
+ --seed 321 \
69
+ --n_iterations 500
70
+ ```
71
+
72
+ Alternatively, you can execute `make example` which runs the above example.
73
+
74
+ See `2rr --help` (and the research paper mentioned at the top) for more information about all the available arguments.
75
+
76
+ <details>
77
+ <summary><b>CLI reference</b></summary>
78
+
79
+ | Option | Type | Default | Description |
80
+ |--------|------|---------|-------------|
81
+ | `--input-file` | text | *required* | Input Excel file with for every team their (un)availability data |
82
+ | `--output-folder` | text | *required* | Folder where the outputs (logs, overview, schedules) will be stored |
83
+ | `--seed` | integer | `None` | Optional seed for `np.random.seed()` |
84
+ | `--unavailable` | text | `NIET` | Cell value to indicate that a team is unavailable |
85
+ | `--clip-bot` | integer | `1` | Value for clipping rest days plot on low end |
86
+ | `--clip-upp` | integer | `41` | Value for clipping rest days plot on high end |
87
+ | `--net / --no-net` | flag | `--no-net` | Report the adjusted number of rest days |
88
+ | `--tabu-length` | integer | `4` | Number of iterations during which a team cannot be selected |
89
+ | `--perturbation-length` | integer | `50` | Check perturbation need every this many iterations |
90
+ | `--n-iterations` | integer | `10000` | Number of tabu phase iterations |
91
+ | `--m` | integer | `7` | Minimum number of time slots between 2 games with same pair of teams |
92
+ | `--p` | integer | `1000` | Cost from dummy supply node q to non-dummy demand node |
93
+ | `--r-max` | integer | `4` | Minimum required time slots for 2 games of same team |
94
+ | `--alpha` | float | `0.5` | Probability of picking perturbation operator 1 |
95
+ | `--beta` | float | `0.01` | Probability of removing a game in operator 1 |
96
+ | `--cost-excessive-rest-days` | float | `500` | Cost for excessive rest days |
97
+
98
+ </details>
99
+
100
+ #### Classes
101
+
102
+ To more freely play around, you can import the core classes in your own Python script or notebook.
103
+
104
+ Here's a minimal example with default parameters:
105
+
106
+ ```python
107
+ from leaguescheduler import InputParser, LeagueScheduler
108
+
109
+ input = InputParser(input_file)
110
+ input.from_excel(sheet_name=input.sheet_names[0])
111
+ input.parse()
112
+
113
+ scheduler = LeagueScheduler(input=input)
114
+ scheduler.construction_phase()
115
+ scheduler.tabu_phase()
116
+
117
+ df = scheduler.create_calendar()
118
+ scheduler.store_calendar(df, file="out/calendar.xlsx")
119
+ ```
120
+
121
+ Type `help(LeagueScheduler)` to show the full documentation.
122
+
123
+ <details>
124
+ <summary><b>Python API reference</b></summary>
125
+
126
+ **`InputParser(filename, unavailable="NIET")`** — Reads and parses input Excel file.
127
+
128
+ **`SchedulerParams(...)`** — Configuration dataclass for the scheduler.
129
+
130
+ | Parameter | Type | Default | Description |
131
+ |-----------|------|---------|-------------|
132
+ | `tabu_length` | int | `4` | Iterations during which a team cannot be selected |
133
+ | `perturbation_length` | int | `50` | Check perturbation need every this many iterations |
134
+ | `n_iterations` | int | `10000` | Number of tabu phase iterations |
135
+ | `m` | int | `7` | Min time slots between 2 games with same pair |
136
+ | `p` | int | `1000` | Cost from dummy supply node to non-dummy demand node |
137
+ | `r_max` | int | `4` | Min required time slots for 2 games of same team |
138
+ | `penalties` | dict | `{}` | Custom penalty mapping for rest days |
139
+ | `alpha` | float | `0.5` | Probability of picking perturbation operator 1 |
140
+ | `beta` | float | `0.01` | Probability of removing a game in operator 1 |
141
+ | `cost_excessive_rest_days` | float | `500` | Cost for excessive rest days |
142
+
143
+ **`LeagueScheduler(input, params=SchedulerParams(), logger=None)`** — Main scheduler class.
144
+
145
+ </details>
146
+
147
+ #### Web application
148
+
149
+ The league scheduler is also made available through a hosted [Streamlit application](https://leaguescheduler.streamlit.app).
150
+
151
+ It has a more limited set of parameters (namely `m`, `r_max`, `n_iterations`, and `penalties`) but can be used out of the box yet without logging.
152
+
153
+ Additionally, the output file includes for every league and by team the distribution of the **number of *adjusted* rest days between games** (meaning that unavailable dates by that team are not considered in the count of the rest days), as well as the **unused home time slots per team**. This facilitates post-analysis of the quality of the generated calendar.
154
+
155
+ If the app sleeps due to inactivity 😴, just wake it back up. You can run the app locally with `make web`.
156
+
157
+ #### Timings
158
+
159
+ How long does the scheduler take? This table sheds some baseline light for a league of **13 teams**:
160
+
161
+ | Iterations | Time |
162
+ |------------------|----------- |
163
+ | 10 | <1s |
164
+ | 100 | <1s |
165
+ | 1k | <1s |
166
+ | 10k | ~3s |
167
+ | 100k | ~25s |
168
+ | 1M | ~245s |
169
+
170
+ *Run on a few years old Windows 10 Pro machine with Intel i7–7700HQ CPU and 32GB RAM.*
171
+
172
+ A few 100(0)s iterations are typically sufficient to arrive at a good schedule.
173
+
174
+ Quite fast. Thanks to Ra-Ra-Rust! 🦀
175
+
176
+ ## Feedback?
177
+
178
+ Let me know if you have any feedback or suggestions for improvement!
@@ -0,0 +1,11 @@
1
+ from leaguescheduler.input_parser import InputParser
2
+ from leaguescheduler.league_scheduler import LeagueScheduler
3
+ from leaguescheduler.params import SchedulerParams
4
+ from leaguescheduler.transportation_problem_solver import TransportationProblemSolver
5
+
6
+ __all__ = [
7
+ "InputParser",
8
+ "SchedulerParams",
9
+ "LeagueScheduler",
10
+ "TransportationProblemSolver",
11
+ ]
@@ -0,0 +1,8 @@
1
+ # NOTE: Should be large enough to avoid overlap with any possible time slot (difference)
2
+ LARGE_NBR = 9999
3
+
4
+ # NOTE: The order of columns should stay date - time - location - home - away!
5
+ OUTPUT_COLS = ["Date", "Hour", "Location", "Home", "Away"]
6
+
7
+ # NOTE: Fixed (so not a parameter)
8
+ MAX_ALLOWED_REST_DAYS = 28
@@ -0,0 +1,109 @@
1
+ import numpy as np
2
+ import pandas as pd
3
+
4
+ from .utils import fill_value
5
+
6
+
7
+ class InputParser:
8
+ """Reads input from Excel file for given league and parses relevant data."""
9
+
10
+ def __init__(self, filename: str, unavailable: str = "NIET") -> None:
11
+ """
12
+ Initializes a new instance of the InputParser class.
13
+
14
+ Every sheet should be structured as follows:
15
+ - Row 1 has the team names T (starting from column 2).
16
+ - Row 2 has the locations for home games (starting from column 2).
17
+ - Column 1 has the league dates S (starting from row 4) [row 3 is for comments].
18
+ - Cells with value 'unavailable' indicate where a column team is not available (A).
19
+ - Cells with another value are considered the home slot hours, e.g., "20h30" (H).
20
+ - Cells with no value are considered baseline available slots.
21
+
22
+ Optionally, a single sheet can be named 'penalties' and contain the penalties
23
+ for the algorithm. The penalties are read as a dictionary with the number of
24
+ days as keys (column 1) and the penalties as values (column 2). In this case,
25
+ the number of days is defined as the exact number of rest days. This class
26
+ internally adds 1 to each key so it conforms to the rest days + 1 convention
27
+ used in SchedulerParams.penalties.
28
+
29
+ :param filename: Path location where input Excel file is stored.
30
+ :param unavailable: Cell value to indicate that a team is unavailable.
31
+ """
32
+ self.unavailable = unavailable
33
+
34
+ self.file = pd.ExcelFile(filename)
35
+ self.sheet_names = [
36
+ sheet_name
37
+ for sheet_name in self.file.sheet_names
38
+ if sheet_name != "penalties"
39
+ ]
40
+
41
+ self.penalties = self.get_penalties() # same regardless of league sheet
42
+
43
+ self.data = None
44
+ self.parsed = False
45
+
46
+ def get_penalties(self) -> dict:
47
+ """Reads penalties (if available) from input Excel file and returns them as a dictionary."""
48
+ penalties = {} # fallback
49
+
50
+ if "penalties" in self.file.sheet_names:
51
+ penalties = (
52
+ pd.read_excel(self.file, sheet_name="penalties", index_col=0)
53
+ .iloc[:, 0]
54
+ .to_dict()
55
+ )
56
+
57
+ # format needs {n_days: penalty} where n_days = rest days + 1 as an int
58
+ # 0 --> e.g., a game on Monday and Tuesday (0 rest days but delta t is 1)
59
+ penalties = {int(k) + 1: v for k, v in penalties.items()}
60
+
61
+ return penalties
62
+
63
+ def from_excel(self, sheet_name: str = None) -> None:
64
+ """Reads data from input Excel file and sheet, then assigns it to self.data."""
65
+ if sheet_name is None or sheet_name in self.sheet_names:
66
+ data = pd.read_excel(self.file, sheet_name=sheet_name)
67
+ data = data.drop(1, axis=0) # drop row at index 1 (row 3 in Excel file)
68
+ self.data = data.reset_index(drop=True)
69
+ else:
70
+ raise ValueError(f"Sheet name {sheet_name} not found in file.")
71
+
72
+ def parse(self) -> None:
73
+ """Extracts (aka parses) relevant data from input file."""
74
+ data = self.data
75
+
76
+ team_names = data.columns[1:]
77
+
78
+ # names of locations for home games
79
+ self.locations = {team_name: data[team_name][0] for team_name in team_names}
80
+
81
+ # get team indices and names (T)
82
+ teams = dict(enumerate(team_names))
83
+
84
+ # get all slots (S)
85
+ dates = pd.to_datetime(data.iloc[1:, 0]) # not necessarily continuous
86
+ slots = dict(enumerate(dates))
87
+
88
+ # process core (i.e. without dates and locations) for remaining sets extraction
89
+ self.core = data.iloc[1:, 1:].reset_index(drop=True)
90
+
91
+ mat = self.core.copy()
92
+ mat = mat.map(fill_value, unavailable=self.unavailable)
93
+ mat = mat.to_numpy()
94
+
95
+ # get all available home slots by team index (H)
96
+ sets_home = {key: np.where(mat[:, key] == 1)[0] for key in teams}
97
+
98
+ # get all non-available away slots by team index (A)
99
+ sets_forbidden = {key: np.where(mat[:, key] == -1)[0] for key in teams}
100
+
101
+ # assemble sets
102
+ self.sets = {
103
+ "teams": teams,
104
+ "slots": slots,
105
+ "home": sets_home,
106
+ "forbidden": sets_forbidden,
107
+ }
108
+
109
+ self.parsed = True