diff --git a/SAS/TMSS/backend/services/scheduling/lib/constraints.py b/SAS/TMSS/backend/services/scheduling/lib/constraints.py index 2bf6d85a8c4c0727c97bca49abdb69ebcf42deb8..c62762388f408e56302ff0b8878573d5c9bc498a 100644 --- a/SAS/TMSS/backend/services/scheduling/lib/constraints.py +++ b/SAS/TMSS/backend/services/scheduling/lib/constraints.py @@ -2021,9 +2021,9 @@ def get_blocking_scheduled_units(scheduling_unit: models.SchedulingUnitBlueprint s.scheduled_observation_start_time <= upper_bound] # Third, loop over the small number of overlapping scheduled units, and only keep the ones sharing one or more stations - candidate_stations = set(scheduling_unit.main_observation_stations) + candidate_stations = set(scheduling_unit.main_observation_specified_stations) observation_overlapping_scheduled_units = [s for s in observation_overlapping_scheduled_units - if any(set(s.main_observation_stations).intersection(candidate_stations))] + if any(set(s.main_observation_used_stations).intersection(candidate_stations))] # Finally, return the result as a queryset, so the caller can do furter queries on it. return scheduled_units.filter(id__in=[s.id for s in observation_overlapping_scheduled_units]).all() diff --git a/SAS/TMSS/backend/src/tmss/tmssapp/models/scheduling.py b/SAS/TMSS/backend/src/tmss/tmssapp/models/scheduling.py index cef39a8c48a73bfceae111e1fba7717506250372..9810dcdb143f7e141eb3952b1250a106eafa5b59 100644 --- a/SAS/TMSS/backend/src/tmss/tmssapp/models/scheduling.py +++ b/SAS/TMSS/backend/src/tmss/tmssapp/models/scheduling.py @@ -227,6 +227,13 @@ class Subtask(RefreshFromDbInvalidatesCachedPropertiesMixin, ProjectPropertyMixi '''is this subtask an observation?''' return self.specifications_template.type.value == SubtaskType.Choices.OBSERVATION.value + @cached_property + def stations(self) -> []: + '''get the used stations if this is an observation. For non-observation subtasks, returns empty list.''' + if self.is_observation: + return self.specifications_doc.get('stations', {}).get('station_list', {}) + return [] + @cached_property def is_using_lofar2_stations(self) -> bool: '''convenience property indicating if this is an observation and if so, if it is using LOFAR2 stations (and only LOFAR2 stations)''' diff --git a/SAS/TMSS/backend/src/tmss/tmssapp/models/specification.py b/SAS/TMSS/backend/src/tmss/tmssapp/models/specification.py index 681a6076d67039d3977bd0a58730b99d082da82d..913e29c20d9d30cc01853001a328c2e0db134ca2 100644 --- a/SAS/TMSS/backend/src/tmss/tmssapp/models/specification.py +++ b/SAS/TMSS/backend/src/tmss/tmssapp/models/specification.py @@ -951,8 +951,9 @@ class SchedulingUnitCommonPropertiesMixin: return datetime.timedelta(seconds=0) @cached_property - def main_observation_stations(self) -> list: - '''return the list of stations of the main observation task''' + def main_observation_specified_stations(self) -> list: + '''return the list of specified stations of the main observation task. + NB: the used stations may differ from the specified stations, due to reservations.''' try: return self.main_observation_task.specified_stations except (ValueError, AttributeError): @@ -1364,52 +1365,13 @@ class SchedulingUnitBlueprint(ProjectPropertyMixin, TemplateSchemaMixin, Schedul ''' return self.draft.scheduling_set.project - @property - def flat_station_list(self): - """ - Get a flat list of stations of the scheduling unit sorted by name - """ - lst_stations = [] - for sublist in self._get_recursively(self.specifications_doc, "stations"): - for item in sublist: - lst_stations.append(item) - return sorted(list(set(lst_stations))) - - @property - def station_groups(self): - """ - Get the station groups of the scheduling unit - """ - lst_station_groups = [] - for sublist in self._get_recursively(self.specifications_doc, "station_groups"): - for item in sublist: - lst_station_groups.append(item) - return lst_station_groups - - def _get_recursively(self, search_dict, field): - """ - Takes a dict with nested lists and dicts, and searches all dicts for a key of the field provided. - """ - fields_found = [] - - for key, value in search_dict.items(): - - if key == field: - fields_found.append(value) - - elif isinstance(value, dict): - results = self._get_recursively(value, field) - for result in results: - fields_found.append(result) - - elif isinstance(value, list): - for item in value: - if isinstance(item, dict): - more_results = self._get_recursively(item, field) - for another_result in more_results: - fields_found.append(another_result) - - return fields_found + @cached_property + def main_observation_used_stations(self) -> list: + '''return the list of used stations of the main observation task''' + try: + return self.main_observation_task.used_stations + except (ValueError, AttributeError): + return [] @property def subtasks(self) -> QuerySet: @@ -1671,6 +1633,15 @@ class TaskBlueprint(ProjectPropertyMixin, TemplateSchemaMixin, TaskCommonPropert '''is this task obsolete (because any of its subtasks are obsolete)''' return self.obsolete_since is not None + @cached_property + def used_stations(self) -> list: + '''return the list of used stations''' + try: + from .scheduling import SubtaskType + return sorted(list(set(sum([obs_subtask.stations for obs_subtask in self.subtasks.filter(obsolete_since__isnull=True).filter(specifications_template__type__value=SubtaskType.Choices.OBSERVATION.value).all()], [])))) + except (ValueError, AttributeError): + return [] + @cached_property def unschedulable_reason(self) -> str: '''unique combination of all unschedulable_reason's of all subtask(s)''' @@ -1814,8 +1785,7 @@ class Reservation(NamedCommon, TemplateSchemaMixin): if "resources" in self.specifications_doc and "stations" in self.specifications_doc["resources"]: stations_to_reserve = self.specifications_doc['resources']['stations'] for sub in active_subs: - # todo: not all the specified stations may actually be in use. Consider only those who were assigned in the end? - conflicting_stations = list(set(sub.flat_station_list).intersection(stations_to_reserve)) + conflicting_stations = list(set(sub.main_observation_used_stations).intersection(stations_to_reserve)) if len(conflicting_stations) != 0: msg = "Station(s) %s cannot be reserved because they are currently in use by scheduling unit %s" % (conflicting_stations, sub) logger.info(msg) diff --git a/SAS/TMSS/backend/src/tmss/tmssapp/tasks.py b/SAS/TMSS/backend/src/tmss/tmssapp/tasks.py index f42827039581dcb6517917386553fb1c8787d897..8b50d9af93017258a5d67f3c91813237f4cff812 100644 --- a/SAS/TMSS/backend/src/tmss/tmssapp/tasks.py +++ b/SAS/TMSS/backend/src/tmss/tmssapp/tasks.py @@ -547,7 +547,7 @@ def create_scheduling_unit_blueprint_and_tasks_and_subtasks_from_scheduling_unit earliest_from = min(between_froms) set_scheduling_unit_blueprint_start_times(scheduling_unit_blueprint, earliest_from, placed=True) - if scheduling_unit_blueprint.interrupts_telescope: + if scheduling_unit_blueprint.interrupts_telescope: # check the trigger accounting, may result in scheduling_unit_blueprint ERROR status # it's up to the user to deal with this trigger unit in error status. scheduling_unit_blueprint.check_trigger_accounting()