Skip to content

Commit

Permalink
feat(core): Parse dates correctly, including syntax sugar
Browse files Browse the repository at this point in the history
Note that most of the parsing code here is extracted from the 2023/08/20 POC.
  • Loading branch information
j3soon committed Aug 10, 2024
1 parent 3cca33b commit d87de38
Show file tree
Hide file tree
Showing 7 changed files with 204 additions and 5 deletions.
7 changes: 3 additions & 4 deletions core/nurse_scheduling/preference_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,9 @@ def shift_request(ctx: Context, preference, preference_idx):
# Note that a shift is represented as (d, r)
# i.e., max(weight * shifts[(d, r, p)]), for all satisfying (d, r)
p = preference.person
preference_days = utils.ensure_list(preference.day)
for _d in preference_days:
# TODO: parse dates
d = _d - 1
preference_days = utils.parse_dates(preference.day, ctx.startdate, ctx.enddate)
for date in preference_days:
d = (date - ctx.startdate).days
r = ctx.map_rid_r[preference.shift]
# Add the objective
weight = 1
Expand Down
24 changes: 24 additions & 0 deletions core/nurse_scheduling/utils.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
import datetime
import re


def ensure_list(val):
if val is None:
return []
Expand All @@ -16,3 +20,23 @@ def ortools_expression_to_bool_var(
model.Add(true_expression).OnlyEnforceIf(var)
model.Add(false_expression).OnlyEnforceIf(var.Not())
return var

def parse_dates(dates, startdate: datetime.date, enddate: datetime.date):
dates = map(str, ensure_list(dates))
parsed_dates = []
for date in dates:
error_details = f'Day: {date}\nStart date: {startdate}\nEnd date: {enddate}\n'
if match := re.match(r'^\d{1,2}$', date):
if startdate.year != enddate.year or startdate.month != enddate.month:
raise ValueError(f'Pure day format (D) is not allowed when start date and end date are not in the same month.\n{error_details}')
parsed_date = datetime.date(startdate.year, startdate.month, int(match.group(0)))
elif match := re.match(r'^(\d{2})-(\d{2})$', date):
if startdate.year != enddate.year:
raise ValueError(f'Pure month-day format (MM-DD) is not allowed when start date and end date are not in the same year.\n{error_details}')
parsed_date = datetime.date(startdate.year, *map(int, match.groups()))
elif match := re.match(r'^(\d{4})-(\d{2})-(\d{2})$', date):
parsed_date = datetime.date(*map(int, match.groups()))
else:
raise ValueError(f'Date is not in the format of YYYY-MM-DD, MM-DD, or D.\n{error_details}')
parsed_dates.append(parsed_date)
return parsed_dates
2 changes: 1 addition & 1 deletion core/tests/test_all.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,5 @@ def test_all():
if actual_csv != expected_csv:
logging.info(f"Actual CSV:\n{actual_csv}")
logging.info(f"Actual output:\n{df}")
logging.info(f"Expected output:\n{pandas.read_csv(io.StringIO(expected_csv))}")
logging.info(f"Expected output:\n{pandas.read_csv( io.StringIO(expected_csv), header=None, keep_default_na=False)}")
pytest.fail(f"Output mismatch for '{base_filepath}'")
7 changes: 7 additions & 0 deletions core/tests/testcases/or-tools-example_2_begin-date.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
,2,3,4,5,6,7,8
,Sat,Sun,Mon,Tue,Wed,Thu,Fri
Nurse 0,N,,N,,N,E,
Nurse 1,,,E,N,D,,N
Nurse 2,E,E,,D,,N,D
Nurse 3,,D,D,E,,D,
Nurse 4,D,N,,,E,,E
81 changes: 81 additions & 0 deletions core/tests/testcases/or-tools-example_2_begin-date.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
apiVersion: alpha
description: OR-Tools Example 2 with shift requests. From <https://developers.google.com/optimization/scheduling/employee_scheduling>.
startdate: 2023-09-02
enddate: 2023-09-08
people:
- description: Nurse 0
- description: Nurse 1
- description: Nurse 2
- description: Nurse 3
- description: Nurse 4
requirements:
- id: D
description: Day shift requirement
required_people: 1
- id: E
description: Evening shift requirement
required_people: 1
- id: N
description: Night shift requirement
required_people: 1
preferences:
- type: all requirements fulfilled
- type: all people work at most one shift per day
- type: assign shifts evenly
# Person 0
- type: shift request
person: 0
day: [2, 6, 8]
shift: N
- type: shift request
person: 0
day: 7
shift: E
# Person 1
- type: shift request
person: 1
day: 6
shift: D
- type: shift request
person: 1
day: [4, 5]
shift: E
- type: shift request
person: 1
day: 8
shift: N
# Person 2
- type: shift request
person: 2
day: 5
shift: D
- type: shift request
person: 2
day: [2, 3, 7]
shift: E
# Person 3
- type: shift request
person: 3
day: [4, 7]
shift: D
- type: shift request
person: 3
day: 5
shift: E
- type: shift request
person: 3
day: 2
shift: N
# Person 4
- type: shift request
person: 4
day: 6
shift: D
- type: shift request
person: 4
day: [4, 7]
shift: E
- type: shift request
person: 4
day: 3
shift: N
7 changes: 7 additions & 0 deletions core/tests/testcases/or-tools-example_2_date-format.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
,2,3,4,5,6,7,8
,Sat,Sun,Mon,Tue,Wed,Thu,Fri
Nurse 0,N,,N,,N,E,
Nurse 1,,,E,N,D,,N
Nurse 2,E,E,,D,,N,D
Nurse 3,,D,D,E,,D,
Nurse 4,D,N,,,E,,E
81 changes: 81 additions & 0 deletions core/tests/testcases/or-tools-example_2_date-format.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
apiVersion: alpha
description: OR-Tools Example 2 with shift requests. From <https://developers.google.com/optimization/scheduling/employee_scheduling>.
startdate: 2023-09-02
enddate: 2023-09-08
people:
- description: Nurse 0
- description: Nurse 1
- description: Nurse 2
- description: Nurse 3
- description: Nurse 4
requirements:
- id: D
description: Day shift requirement
required_people: 1
- id: E
description: Evening shift requirement
required_people: 1
- id: N
description: Night shift requirement
required_people: 1
preferences:
- type: all requirements fulfilled
- type: all people work at most one shift per day
- type: assign shifts evenly
# Person 0
- type: shift request
person: 0
day: [2023-09-02, 2023-09-06, 2023-09-08]
shift: N
- type: shift request
person: 0
day: 2023-09-07
shift: E
# Person 1
- type: shift request
person: 1
day: 2023-09-06
shift: D
- type: shift request
person: 1
day: [2023-09-04, 2023-09-05]
shift: E
- type: shift request
person: 1
day: 2023-09-08
shift: N
# Person 2
- type: shift request
person: 2
day: 09-05
shift: D
- type: shift request
person: 2
day: [09-02, 09-03, 09-07]
shift: E
# Person 3
- type: shift request
person: 3
day: [09-04, 09-07]
shift: D
- type: shift request
person: 3
day: 09-05
shift: E
- type: shift request
person: 3
day: 09-02
shift: N
# Person 4
- type: shift request
person: 4
day: 6
shift: D
- type: shift request
person: 4
day: [4, 7]
shift: E
- type: shift request
person: 4
day: 3
shift: N

0 comments on commit d87de38

Please sign in to comment.