diff --git a/SAS/TMSS/src/tmss/tmssapp/models/specification.py b/SAS/TMSS/src/tmss/tmssapp/models/specification.py index 353f7a16ea0eb7c3f5977161162336f3214e675a..7ec6f980a09fb9dd3d765efb164611a5d898b8a6 100644 --- a/SAS/TMSS/src/tmss/tmssapp/models/specification.py +++ b/SAS/TMSS/src/tmss/tmssapp/models/specification.py @@ -660,6 +660,21 @@ class SchedulingUnitBlueprint(NamedCommon): else: return None + @property + def observed_end_time(self) -> datetime or None: + """ + return the latest stop time of all (observation) tasks of this scheduling unit with the status observed/finished + """ + observed_tasks = [] + for task in self.task_blueprints.all(): + if task.specifications_template.type.value == TaskType.Choices.OBSERVATION.value and\ + (task.status == "observed" or task.status == "finished") and task.stop_time is not None: + observed_tasks.append(task) + if observed_tasks: + return max(observed_tasks, key=lambda x: x.stop_time).stop_time + else: + return None + @property def status(self): """ @@ -800,14 +815,19 @@ class TaskDraft(NamedCommon): ''' scheduling_relations = list(self.first_to_connect.all()) + list(self.second_to_connect.all()) for scheduling_relation in scheduling_relations: - if scheduling_relation.first.id == self._id and scheduling_relation.placement_id == "after": + # sometimes self._id does not exist so use self.id instead to avoid Exception + if hasattr(self, '_id'): + id = self._id + else: + id = self.id + if scheduling_relation.first.id == id and scheduling_relation.placement_id == "after": previous_related_task_draft = TaskDraft.objects.get(id=scheduling_relation.second.id) time_offset = scheduling_relation.time_offset # todo: max of several relations if previous_related_task_draft.relative_stop_time: return previous_related_task_draft.relative_stop_time + datetime.timedelta(seconds=time_offset) - if scheduling_relation.second.id == self._id and scheduling_relation.placement_id == "before": + if scheduling_relation.second.id == id and scheduling_relation.placement_id == "before": previous_related_task_draft = TaskDraft.objects.get(id=scheduling_relation.first.id) time_offset = scheduling_relation.time_offset # todo: max of several relations @@ -872,6 +892,7 @@ class TaskDraft(NamedCommon): class TaskBlueprint(NamedCommon): + specifications_doc = JSONField(help_text='Schedulings for this task (IMMUTABLE).') do_cancel = BooleanField(help_text='Cancel this task.') specifications_template = ForeignKey('TaskTemplate', on_delete=CASCADE, help_text='Schema used for specifications_doc (IMMUTABLE).') @@ -917,20 +938,25 @@ class TaskBlueprint(NamedCommon): ''' scheduling_relations = list(self.first_to_connect.all()) + list(self.second_to_connect.all()) for scheduling_relation in scheduling_relations: - if scheduling_relation.first.id == self._id and scheduling_relation.placement_id == "after": # self.id and placement.value will hit the db, this does not + # sometimes self._id does not exist so use self.id instead to avoid Exception + if hasattr(self, '_id'): + id = self._id + else: + id = self.id + if scheduling_relation.first.id == id and scheduling_relation.placement_id == "after": # self.id and placement.value will hit the db, this does not previous_related_task_blueprint = TaskBlueprint.objects.get(id=scheduling_relation.second.id) time_offset = scheduling_relation.time_offset # todo: max of several relations if previous_related_task_blueprint.relative_stop_time: return previous_related_task_blueprint.relative_stop_time + datetime.timedelta(seconds=time_offset) - if scheduling_relation.second.id == self._id and scheduling_relation.placement_id == "before": # self.id and placement.value will hit the db, this does not + if scheduling_relation.second.id == id and scheduling_relation.placement_id == "before": # self.id and placement.value will hit the db, this does not previous_related_task_blueprint = TaskBlueprint.objects.get(id=scheduling_relation.first.id) time_offset = scheduling_relation.time_offset # todo: max of several relations if previous_related_task_blueprint.relative_stop_time: return previous_related_task_blueprint.relative_stop_time + datetime.timedelta(seconds=time_offset) - return datetime.timedelta(seconds=666660) + return datetime.timedelta(seconds=0) @cached_property def relative_stop_time(self) -> datetime.timedelta: diff --git a/SAS/TMSS/src/tmss/tmssapp/serializers/specification.py b/SAS/TMSS/src/tmss/tmssapp/serializers/specification.py index bf250c5a51a2781970924e9ec30eb415d147b9fe..279d0ae76212c863bdbb69146dc0cebe9c375612 100644 --- a/SAS/TMSS/src/tmss/tmssapp/serializers/specification.py +++ b/SAS/TMSS/src/tmss/tmssapp/serializers/specification.py @@ -314,7 +314,7 @@ class SchedulingUnitBlueprintSerializer(RelationalHyperlinkedModelSerializer): class Meta: model = models.SchedulingUnitBlueprint fields = '__all__' - extra_fields = ['task_blueprints', 'duration', 'start_time', 'stop_time', 'status'] + extra_fields = ['task_blueprints', 'duration', 'start_time', 'stop_time', 'status', 'observed_end_time'] class SchedulingUnitBlueprintCopyToSchedulingUnitDraftSerializer(SchedulingUnitBlueprintSerializer): class Meta(SchedulingUnitDraftSerializer.Meta): diff --git a/SAS/TMSS/test/t_scheduling.py b/SAS/TMSS/test/t_scheduling.py index 116f201108242191c8b5ea2782c6600f7c5f74a8..ec9de7be402f90143072687153d34c53c6bac89a 100755 --- a/SAS/TMSS/test/t_scheduling.py +++ b/SAS/TMSS/test/t_scheduling.py @@ -400,27 +400,37 @@ class SAPTest(unittest.TestCase): self.assertEqual(dp2_in.sap, dp2_out.sap) - -class CreationFromSchedulingUnitDraft(unittest.TestCase): +class TestWithUC1Specifications(unittest.TestCase): """ - From scheduling_unit_draft test: - create_task_blueprints_and_subtasks_from_scheduling_unit_draft(scheduling_unit_draft: models.SchedulingUnitDraft) -> models.SchedulingUnitBlueprint: - This requires Resource Assigner testenvironment being alive + The Setup will create Scheduling Unit Draft with UC1 strategy template + It will use the function 'create_task_blueprints_and_subtasks_from_scheduling_unit_draft' which is then + implicit tested. + Create Task Blueprints and Subtasks: + Observation Task 'Calibration 1' + SubTask Observation Control + SubTask QA File + SubTask QA Plots + Pipeline Task 'Pipeline 1' + SubTask Pipeline Control + Observation Task 'Target Observation' + SubTask Observation Control + SubTask QA File + SubTask QA Plots + Pipeline Task 'Pipeline target1' + SubTask Pipeline Control + Pipeline Task 'Pipeline target2' + SubTask Pipeline Control + Observation Task 'Calibration 2' + SubTask Observation Control + SubTask QA File + SubTask QA Plots + Pipeline Task 'Pipeline 2' + SubTask Pipeline Control + + Note that this test requires Resource Assigner testenvironment being alive """ - - def test_create_task_blueprints_and_subtasks_from_scheduling_unit_draft_with_UC1_requirements(self): - """ - Create Scheduling Unit Draft with requirements_doc (read from file) - Create Task Blueprints and Subtasks - Check if tasks (7) are created: - Calibration 1 : 1 Observation and 1 Pipeline task - Target Observation: 1 Observation and 2 Pipeline tasks - Calibration 2 : 1 Observation and 1 Pipeline task - Check if subtasks (13) are created: - Every Observation Task: 3 subtasks (1 control, 2 QA) - Every Pipeline Task: 1 subtasks (1 control) - makes 3x3 + 4x1 = 13 - """ + @classmethod + def setUpClass(cls) -> None: strategy_template = models.SchedulingUnitObservingStrategyTemplate.objects.get(name="UC1 CTC+pipelines") scheduling_unit_draft = models.SchedulingUnitDraft.objects.create( @@ -436,20 +446,81 @@ class CreationFromSchedulingUnitDraft(unittest.TestCase): create_task_blueprints_and_subtasks_from_scheduling_unit_draft(scheduling_unit_draft) scheduling_unit_draft.refresh_from_db() - task_drafts = scheduling_unit_draft.task_drafts.all() - self.assertEqual(7, len(task_drafts)) - - scheduling_unit_blueprints = scheduling_unit_draft.scheduling_unit_blueprints.all() - self.assertEqual(1, len(scheduling_unit_blueprints)) + cls.task_drafts = scheduling_unit_draft.task_drafts.all() + cls.scheduling_unit_blueprints = scheduling_unit_draft.scheduling_unit_blueprints.all() + cls.scheduling_unit_blueprint = cls.scheduling_unit_blueprints[0] + cls.task_blueprints = cls.scheduling_unit_blueprint.task_blueprints.all() - scheduling_unit_blueprint = scheduling_unit_blueprints[0] - task_blueprints = scheduling_unit_blueprint.task_blueprints.all() - self.assertEqual(7, len(task_blueprints)) + def test_create_task_blueprints_and_subtasks_from_scheduling_unit_draft(self): + """ + Create Task Blueprints and Subtasks (class setup) + Check if tasks (7) are created: + Calibration 1 : 1 Observation and 1 Pipeline task + Target Observation: 1 Observation and 2 Pipeline tasks + Calibration 2 : 1 Observation and 1 Pipeline task + Check if subtasks (13) are created: + Every Observation Task: 3 subtasks (1 control, 2 QA) + Every Pipeline Task: 1 subtasks (1 control) + makes 3x3 + 4x1 = 13 + """ + self.assertEqual(7, len(self.task_drafts)) + self.assertEqual(1, len(self.scheduling_unit_blueprints)) + self.assertEqual(7, len(self.task_blueprints)) total_subtasks = 0 - for task_blueprint in task_blueprints: + for task_blueprint in self.task_blueprints: total_subtasks += task_blueprint.subtasks.count() self.assertEqual(13, total_subtasks) + def test_relative_times(self): + """ + Create Task Blueprints and Subtasks (class setup) + Set start and stop times of taskBlueprint + Set the subtask start/stop time equal to its taskBlueprint + Set all subtask states to 'finished' + Check the observed_end_time of the SchedulingUnitBlueprint + Check the relative_start/stop_time of the SchedulingUnitBlueprint + start = 0 + stop = calculates like 8hours (Target) + 2x10min (calibrators) + 2*1min (offset between observations) = 8h22min + """ + DATETIME_FORMAT = "%Y-%m-%d %H:%M:%S" + test_timeschedule = { + # name of taskBlueprint start_time stop_time + "Calibrator Observation 1": ["2020-11-01 08:00:00", "2020-11-01 08:10:00"], + "Pipeline 1": ["2020-11-01 08:20:00", "2020-11-01 08:22:00"], + "Target Observation": ["2020-11-01 08:30:00", "2020-11-01 18:00:00"], + "Pipeline target1": ["2020-11-01 18:30:00", "2020-11-01 18:35:00"], + "Pipeline target2": ["2020-11-01 18:40:00", "2020-11-01 18:45:00"], + "Calibrator Observation 2": ["2020-11-01 19:00:00", "2020-11-01 19:20:00"], + "Pipeline 2": ["2020-11-01 19:30:00", "2020-11-01 19:40:00"] + } + # Set time_schedule, + for name, times in test_timeschedule.items(): + task_blueprint = list(filter(lambda x: x.name == name, self.task_blueprints))[0] + for subtask in task_blueprint.subtasks.all(): + subtask.state = models.SubtaskState.objects.get(value="finished") + subtask.stop_time = datetime.strptime(times[1], DATETIME_FORMAT) + subtask.start_time = datetime.strptime(times[0], DATETIME_FORMAT) + subtask.save() + + # Check times + self.assertEqual("2020-11-01 19:20:00", self.scheduling_unit_blueprint.observed_end_time.strftime("%Y-%m-%d %H:%M:%S")) + self.assertEqual(timedelta(0), self.scheduling_unit_blueprint.relative_start_time) + self.assertEqual(timedelta(hours=8, minutes=22), self.scheduling_unit_blueprint.relative_stop_time) + + for task_blueprint in self.task_blueprints: + if task_blueprint.name == "Calibrator Observation 1": + self.assertEqual(timedelta(0), task_blueprint.relative_start_time) + self.assertEqual(timedelta(minutes=10), task_blueprint.relative_stop_time) + elif task_blueprint.name == "Target Observation": + self.assertEqual(timedelta(minutes=11), task_blueprint.relative_start_time) + self.assertEqual(timedelta(hours=8, minutes=11), task_blueprint.relative_stop_time) + elif task_blueprint.name == "Calibrator Observation 2": + self.assertEqual(timedelta(hours=8, minutes=12), task_blueprint.relative_start_time) + self.assertEqual(timedelta(hours=8, minutes=22), task_blueprint.relative_stop_time) + else: + self.assertEqual(timedelta(0), task_blueprint.relative_start_time) + self.assertEqual(timedelta(0), task_blueprint.relative_stop_time) + if __name__ == "__main__": os.environ['TZ'] = 'UTC'