diff --git a/SAS/TMSS/backend/services/scheduling/lib/constraints/template_constraints_v1.py b/SAS/TMSS/backend/services/scheduling/lib/constraints/template_constraints_v1.py index 6d6ce315a862c88264d498256b049320439537f0..271ba25a5a23c43672976e87a6c540195595ce9a 100644 --- a/SAS/TMSS/backend/services/scheduling/lib/constraints/template_constraints_v1.py +++ b/SAS/TMSS/backend/services/scheduling/lib/constraints/template_constraints_v1.py @@ -207,9 +207,9 @@ def can_run_anywhere_within_timewindow_with_time_constraints(scheduling_unit: mo for between in constraints['time']['between']: time_from = parser.parse(between["from"], ignoretz=True) time_to = parser.parse(between["to"], ignoretz=True) - if time_from >= lower_bound and time_to <= upper_bound: + if time_from <= lower_bound and time_to >= upper_bound: can_run_between = True - break # constraint window completely inside the boundary, so True and don't look any further + break # constraint window completely covering the boundary, so True and don't look any further else: can_run_between = False diff --git a/SAS/TMSS/backend/services/scheduling/test/t_dynamic_scheduling.py b/SAS/TMSS/backend/services/scheduling/test/t_dynamic_scheduling.py index 67dac6be26d03220787b83980cc234ba258c2806..eea170dc26987c54a43702af4168c15872f9538c 100755 --- a/SAS/TMSS/backend/services/scheduling/test/t_dynamic_scheduling.py +++ b/SAS/TMSS/backend/services/scheduling/test/t_dynamic_scheduling.py @@ -991,22 +991,59 @@ class TestTimeConstraints(TestCase): self.assertFalse(tc1.can_run_anywhere_within_timewindow_with_time_constraints(self.scheduling_unit_blueprint, datetime(2020, 1, 1, 12, 0, 0), datetime(2020, 1, 2, 12, 0, 0))) + # Set datetime constraints start > lower_bound and stop < upper_bound + self.clear_time_constraints() + self.add_time_between_constraint(datetime(2020, 1, 1, 18, 0, 0), datetime(2020, 1, 1, 19, 0, 0)) + self.assertFalse(tc1.can_run_anywhere_within_timewindow_with_time_constraints(self.scheduling_unit_blueprint, + datetime(2020, 1, 1, 12, 0, 0), datetime(2020, 1, 2, 12, 0, 0))) def test_can_run_anywhere_between_returns_true(self): """ - Test 'between' constraint with start/stop datetime constraints 'inside' upper_bound and lower_bound + Test 'between' constraint with start/stop datetime constraints 'outside' upper_bound and lower_bound """ - # Set datetime constraints start > lower_bound and stop < upper_bound -duration + # Set datetime constraints start < lower_bound and stop > upper_bound self.clear_time_constraints() - self.add_time_between_constraint(datetime(2020, 1, 1, 13, 0, 0), datetime(2020, 1, 1, 15, 0, 0)) + self.add_time_between_constraint(datetime(2020, 1, 1, 11, 0, 0), datetime(2020, 1, 2, 13, 0, 0)) self.assertTrue(tc1.can_run_anywhere_within_timewindow_with_time_constraints(self.scheduling_unit_blueprint, - datetime(2020, 1, 1, 12, 0, 0), datetime(2020, 1, 1, 20, 0, 0))) + datetime(2020, 1, 1, 12, 0, 0), datetime(2020, 1, 2, 12, 0, 0))) - # Set datetime constraints start = lower_bound and stop = upper_bound - duration + # Set datetime constraints start = lower_bound and stop = upper_bound self.clear_time_constraints() - self.add_time_between_constraint(datetime(2020, 1, 1, 13, 0, 0), datetime(2020, 1, 1, 15, 0, 0)) + self.add_time_between_constraint(datetime(2020, 1, 1, 12, 0, 0), datetime(2020, 1, 2, 12, 0, 0)) self.assertTrue(tc1.can_run_anywhere_within_timewindow_with_time_constraints(self.scheduling_unit_blueprint, - datetime(2020, 1, 1, 13, 0, 0), datetime(2020, 1, 1, 17, 10, 0))) + datetime(2020, 1, 1, 12, 0, 0), datetime(2020, 1, 2, 12, 0, 0))) + + def test_can_run_within_between_returns_true(self): + """ + Test 'between' constraint with start/stop datetime constraints (within, not anywhere within) + """ + # Set datetime constraints start > lower_bound and stop > upper_bound, large window + self.clear_time_constraints() + self.add_time_between_constraint(datetime(2020, 1, 1, 13, 0, 0), datetime(2020, 1, 2, 12, 0, 0)) + self.assertTrue(tc1.can_run_within_timewindow_with_time_constraints(self.scheduling_unit_blueprint, + datetime(2020, 1, 1, 12, 0, 0), datetime(2020, 1, 1, 20, 0, 0))) + + # Set datetime constraints start = lower_bound and stop = upper_bound, window just large enough for obs + self.clear_time_constraints() + self.add_time_between_constraint(datetime(2020, 1, 1, 12, 0, 0), datetime(2020, 1, 1, 14, 0, 0)) + self.assertTrue(tc1.can_run_within_timewindow_with_time_constraints(self.scheduling_unit_blueprint, + datetime(2020, 1, 1, 12, 0, 0), datetime(2020, 1, 1, 14, 10, 0))) + + def test_can_run_within_between_returns_false(self): + """ + Test 'between' constraint with start/stop datetime constraints (within, not anywhere within) + """ + # Set datetime constraints start < lower_bound and stop < upper_bound, too little overlap for obs + self.clear_time_constraints() + self.add_time_between_constraint(datetime(2020, 1, 1, 10, 0, 0), datetime(2020, 1, 1, 13, 0, 0)) + self.assertFalse(tc1.can_run_within_timewindow_with_time_constraints(self.scheduling_unit_blueprint, + datetime(2020, 1, 1, 12, 0, 0), datetime(2020, 1, 1, 20, 0, 0))) + + # Set datetime constraints start > lower_bound and stop < upper_bound, constraint window too small for obs + self.clear_time_constraints() + self.add_time_between_constraint(datetime(2020, 1, 1, 14, 0, 0), datetime(2020, 1, 1, 15, 0, 0)) + self.assertFalse(tc1.can_run_within_timewindow_with_time_constraints(self.scheduling_unit_blueprint, + datetime(2020, 1, 1, 12, 0, 0), datetime(2020, 1, 1, 20, 10, 0))) # 'not between' contraint @@ -1087,37 +1124,38 @@ class TestTimeConstraints(TestCase): self.clear_time_constraints() self.assertTrue(self.execute_can_run_within_timewindow_with_time_constraints_of_24hour_boundary()) - # Add constraints of 1hr, we still 'can_run' + # Add constraints of 1hr, we cannot run self.add_time_between_constraint(datetime(2020, 1, 1, 13, 0, 0), datetime(2020, 1, 1, 14, 0, 0)) self.add_time_between_constraint(datetime(2020, 1, 1, 16, 0, 0), datetime(2020, 1, 1, 17, 0, 0)) - self.assertTrue(self.execute_can_run_within_timewindow_with_time_constraints_of_24hour_boundary()) + self.assertFalse(self.execute_can_run_within_timewindow_with_time_constraints_of_24hour_boundary()) - # Add constraints of 2hr, we still 'can_run' + # Add constraints of 2hr, but partially outside the bounds, we still cannot run self.add_time_between_constraint(datetime(2020, 1, 2, 11, 0, 0), datetime(2020, 1, 2, 13, 0, 0)) + self.assertFalse(self.execute_can_run_within_timewindow_with_time_constraints_of_24hour_boundary()) + + # Add constraints of 2hr, we can run again + self.add_time_between_constraint(datetime(2020, 1, 1, 17, 0, 0), datetime(2020, 1, 1, 19, 0, 0)) self.assertTrue(self.execute_can_run_within_timewindow_with_time_constraints_of_24hour_boundary()) # Add constraint of 24hr constraint, we still 'can_run' self.add_time_between_constraint(datetime(2020, 1, 1, 12, 0, 0), datetime(2020, 1, 2, 12, 0, 0)) self.assertTrue(self.execute_can_run_within_timewindow_with_time_constraints_of_24hour_boundary()) - # Add constraint of 2hr, to fill the 'last gap', we 'can run' - self.add_time_between_constraint(datetime(2020, 1, 2, 10, 0, 0), datetime(2020, 1, 2, 12, 0, 0)) - self.assertTrue(self.execute_can_run_within_timewindow_with_time_constraints_of_24hour_boundary()) - # Clear all between constraints self.clear_time_constraints() - # Add constraints 'outside' the 24hr, now we 'can not run' - self.add_time_between_constraint(datetime(2020, 1, 2, 13, 0, 0), datetime(2020, 1, 2, 14, 0, 0)) - self.add_time_between_constraint(datetime(2020, 1, 2, 16, 0, 0), datetime(2020, 1, 2, 17, 0, 0)) + # Add constraints after the 24hr, now we 'can not run' + self.add_time_between_constraint(datetime(2020, 1, 2, 13, 0, 0), datetime(2020, 1, 2, 15, 0, 0)) + self.add_time_between_constraint(datetime(2020, 1, 2, 16, 0, 0), datetime(2020, 1, 2, 20, 0, 0)) self.assertFalse(self.execute_can_run_within_timewindow_with_time_constraints_of_24hour_boundary()) - # Add constraint 'outside' the 24hr, we 'still can not run' + # Add constraint before the 24hr, we 'still can not run' self.add_time_between_constraint(datetime(2020, 1, 1, 9, 0, 0), datetime(2020, 1, 1, 12, 0, 0)) self.assertFalse(self.execute_can_run_within_timewindow_with_time_constraints_of_24hour_boundary()) - # add one 'inside' constraint, 1 hour within block of 2 hour so overall must be ok - self.add_time_between_constraint(datetime(2020, 1, 1, 13, 30, 0), datetime(2020, 1, 1, 14, 30, 0)) + # add one 'inside' constraint of 3 hours, so overall must be ok again. + # Note that 2 hrs would only be sufficient if they match the moving window exactly (here: full hour) + self.add_time_between_constraint(datetime(2020, 1, 1, 14, 30, 0), datetime(2020, 1, 1, 17, 30, 0)) self.assertTrue(self.execute_can_run_within_timewindow_with_time_constraints_of_24hour_boundary()) def test_can_run_within_not_between_constraints(self): @@ -1155,8 +1193,53 @@ class TestTimeConstraints(TestCase): self.add_time_not_between_constraint(datetime(2020, 1, 1, 12, 0, 0), datetime(2020, 1, 1, 16, 0, 0)) self.assertTrue(self.execute_can_run_within_timewindow_with_time_constraints_of_24hour_boundary()) - # todo: test several different time constraints, make sure only true when all combined leave a large enough - # gap for the obs + # combined time contraints tests + + def test_can_run_anywhere_combined_time_constraints(self): + """ + Test multiple time constraints in combination and make sure that they block the time window as expected, + even though each constraint individually would allow the observation to run. + """ + + # Set before and after constraint with sufficient gap to fit observation, and assert True + self.clear_time_constraints() + self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['time']["after"] = datetime(2020, 1, 1, 12, 59, 59).isoformat() + self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['time']["before"] = datetime(2020, 1, 1, 15, 0, 1).isoformat() + self.assertTrue(self.execute_can_run_within_timewindow_with_time_constraints_of_24hour_boundary()) + + # set before and after constraint with slightly smaller gap for observation, and assert False + self.clear_time_constraints() + self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['time']["after"] = datetime(2020, 1, 1, 13, 0, 0).isoformat() + self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['time']["before"] = datetime(2020, 1, 1, 15, 0, 0).isoformat() + self.assertFalse(self.execute_can_run_within_timewindow_with_time_constraints_of_24hour_boundary()) + + # set before and after constraint with large gap + # then and add additional between and not between constraints until window is blocked + # can run 13-8h + self.clear_time_constraints() + self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['time']["after"] = datetime(2020, 1, 1, 13, 0, 0).isoformat() + self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['time']["before"] = datetime(2020, 1, 2, 8, 0, 0).isoformat() + self.assertTrue(self.execute_can_run_within_timewindow_with_time_constraints_of_24hour_boundary()) + + # can run 13h-20h + self.add_time_between_constraint(datetime(2020, 1, 1, 11, 0, 0), datetime(2020, 1, 1, 20, 0, 0)) + self.assertTrue(self.execute_can_run_within_timewindow_with_time_constraints_of_24hour_boundary()) + + # can run 13h-17h + self.add_time_not_between_constraint(datetime(2020, 1, 1, 17, 0, 0), datetime(2020, 1, 2, 4, 0, 0)) + self.assertTrue(self.execute_can_run_within_timewindow_with_time_constraints_of_24hour_boundary()) + + # can not run anymore + self.add_time_not_between_constraint(datetime(2020, 1, 1, 12, 0, 0), datetime(2020, 1, 1, 16, 0, 0)) + self.assertFalse(self.execute_can_run_within_timewindow_with_time_constraints_of_24hour_boundary()) + + # add another between window, can run 4h-8h + self.add_time_between_constraint(datetime(2020, 1, 1, 2, 0, 0), datetime(2020, 1, 2, 12, 0, 0)) + self.assertTrue(self.execute_can_run_within_timewindow_with_time_constraints_of_24hour_boundary()) + + # move before constraint, can not run anymore + self.scheduling_unit_blueprint.draft.scheduling_constraints_doc['time']["before"] = datetime(2020, 1, 2, 5, 0, 0).isoformat() + self.assertFalse(self.execute_can_run_within_timewindow_with_time_constraints_of_24hour_boundary()) # class TestReservedStations(unittest.TestCase):