diff --git a/SAS/TMSS/backend/src/tmss/tmssapp/models/specification.py b/SAS/TMSS/backend/src/tmss/tmssapp/models/specification.py index 082d0dd9dc73fc3450d03ebb2c3970f3dce7a6b0..56eb514b26f492bfa9ca258b34c9090c10804792 100644 --- a/SAS/TMSS/backend/src/tmss/tmssapp/models/specification.py +++ b/SAS/TMSS/backend/src/tmss/tmssapp/models/specification.py @@ -1138,6 +1138,38 @@ class SchedulingUnitCommonPropertiesMixin: '''return the latest possible start time for this unit's project and cycle(s)''' return self.project.latest_possible_cycle_stop_time + def validate_scheduling_constraints(self): + '''validate the scheduling_constraints_doc against its template, and for additional consistency checks''' + if self.scheduling_constraints_template is None or self.scheduling_constraints_doc is None: + return + + self.scheduling_constraints_template.validate_document(self.scheduling_constraints_doc) + + if 'time' in self.scheduling_constraints_doc: + if 'between' in self.scheduling_constraints_doc['time']: + between_durations = [parser.parse(between["to"], ignoretz=True)-parser.parse(between["from"], ignoretz=True) + for between in self.scheduling_constraints_doc['time']['between']] + + if any([between_duration < self.specified_main_observation_duration for between_duration in between_durations]): + raise RuleValidationException("A between constraint is shorter than the main observation's duration.") + + if 'sky' in self.scheduling_constraints_doc: + if 'transit_offset' in self.scheduling_constraints_doc['sky']: + # check if the time between all target transits is not larger than the transit_window_width + transit_window_width_seconds = abs(self.scheduling_constraints_doc['sky']['transit_offset']['to'] - self.scheduling_constraints_doc['sky']['transit_offset']['from']) + # ToDo: move get_transit_offset_pointings (and similar) methods to Task/SchedulingUnit + from lofar.sas.tmss.services.scheduling.constraints import get_transit_offset_pointings + for task in self.observation_tasks: + if task.is_target_observation: + pointings = get_transit_offset_pointings(task) + transits = [p.transit_lst for p in pointings] + earliest_transit = min(transits) + latest_transit = max(transits) + delta_transit = datetime.datetime.combine(datetime.date.today(), latest_transit) - datetime.datetime.combine(datetime.date.today(), earliest_transit) + delta_transit_seconds = delta_transit.total_seconds() + if delta_transit_seconds > transit_window_width_seconds: + raise RuleValidationException("time between latest and earliest transit for targets in task id=%s name='%s' su_id=%s is larger than the transit_offset window itself: delta_transit=%dsec > transit_window_width=%ssec" % (task.id, task.name, self.id, delta_transit_seconds, transit_window_width_seconds)) + class SchedulingUnitDraft(TemplateSchemaMixin, ProjectPropertyMixin, SchedulingUnitCommonPropertiesMixin, RefreshFromDbInvalidatesCachedPropertiesMixin, NamedCommon): scheduling_set = ForeignKey('SchedulingSet', related_name='scheduling_unit_drafts', on_delete=CASCADE, help_text='Set to which this scheduling unit draft belongs.') @@ -1171,9 +1203,8 @@ class SchedulingUnitDraft(TemplateSchemaMixin, ProjectPropertyMixin, SchedulingU # we intentionally do not add defaults to scheduling_constraints_doc # because that would add default items which the user did not specify, - # and for scheduling contraints this can have unforeseen sideeffects that the user did not intent. - self.annotate_doc_using_template('scheduling_constraints_doc', 'scheduling_constraints_template') - self.validate_doc_using_template('scheduling_constraints_doc', 'scheduling_constraints_template') + # and for scheduling constraints this can have unforeseen side effects that the user did not intend. + self.validate_scheduling_constraints() super().save(force_insert, force_update, using, update_fields) @@ -1336,17 +1367,9 @@ class SchedulingUnitBlueprint(ProjectPropertyMixin, TemplateSchemaMixin, Schedul if hasattr(self, 'draft'): setattr(self, copy_field, getattr(self.draft, copy_field)) - # enforce fixed_time scheduler when time.at is given - # ToDo: do we actually want this? It seems to break some tests. - # I'm still in favor of removing the 'scheduler'='dynamic'/'fixed_time' property, - # and making it implicit: if 'time'at' is defined, then it's fixed by definition - # and if 'time'at' is not defined, then it's dynamic by definition - # if self.scheduling_constraints_doc is not None and self.scheduling_constraints_doc.get('time', {}).get('at') is not None: - # self.scheduling_constraints_doc['scheduler'] = 'fixed_time' - # we intentionally do not add defaults to scheduling_constraints_doc # because that would add default items which the user did not specify, - # and for scheduling contraints this can have unforeseen sideeffects that the user did not intent. + # and for scheduling contraints this can have unforeseen side-effects that the user did not intent. # but, we do check the validity of the constraints self.validate_scheduling_constraints() @@ -1358,20 +1381,6 @@ class SchedulingUnitBlueprint(ProjectPropertyMixin, TemplateSchemaMixin, Schedul super().save(force_insert, force_update, using, update_fields) - def validate_scheduling_constraints(self): - '''validate the scheduling_constraints_doc against its template, and for additional consistency checks''' - if self.scheduling_constraints_template is None or self.scheduling_constraints_doc is None: - return - - self.scheduling_constraints_template.validate_document(self.scheduling_constraints_doc) - - if 'time' in self.scheduling_constraints_doc: - if 'between' in self.scheduling_constraints_doc['time']: - between_durations = [parser.parse(between["to"], ignoretz=True)-parser.parse(between["from"], ignoretz=True) - for between in self.scheduling_constraints_doc['time']['between']] - - if any([between_duration < self.specified_main_observation_duration for between_duration in between_durations]): - raise RuleValidationException("A between constraint is shorter than the main observation's duration.") @property def tasks(self) -> QuerySet: diff --git a/SAS/TMSS/backend/src/tmss/tmssapp/tasks.py b/SAS/TMSS/backend/src/tmss/tmssapp/tasks.py index 97faa90d06c50b6d115cff6da144d4472d49b5d3..4aab0b865a25b65e95051e876c79b3a3ce308536 100644 --- a/SAS/TMSS/backend/src/tmss/tmssapp/tasks.py +++ b/SAS/TMSS/backend/src/tmss/tmssapp/tasks.py @@ -435,6 +435,9 @@ def update_task_graph_from_specifications_doc(scheduling_unit_draft: models.Sche scheduling_unit_draft.scheduling_constraints_doc = specifications_doc['scheduling_constraints_doc'] scheduling_unit_draft.save() + # finally, check the scheduling_constraints (may raise, reverting the transaction) + scheduling_unit_draft.validate_scheduling_constraints() + scheduling_unit_draft.refresh_from_db() return scheduling_unit_draft @@ -642,6 +645,9 @@ def update_task_blueprints_and_subtasks_graph_from_draft(scheduling_unit_bluepri # and apply update_subtasks_start_times_for_scheduling_unit(scheduling_unit_blueprint, initial_scheduled_start_time) + # finally, check the scheduling_constraints (may raise, reverting the transaction) + scheduling_unit_blueprint.validate_scheduling_constraints() + # refresh so all related fields are updated. scheduling_unit_blueprint.refresh_from_db()