From ee97c71c8ef9070be5f73930e5d91c753d4f5c16 Mon Sep 17 00:00:00 2001 From: Moira Andrews Date: Wed, 1 Apr 2026 12:09:07 -0400 Subject: [PATCH 1/7] update advance window to take the scheduled start and end times instead of the full cadence window start and end --- .../cadences/resume_cadence_after_failure.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/tom_observations/cadences/resume_cadence_after_failure.py b/tom_observations/cadences/resume_cadence_after_failure.py index cfc663d8a..963a74bf8 100644 --- a/tom_observations/cadences/resume_cadence_after_failure.py +++ b/tom_observations/cadences/resume_cadence_after_failure.py @@ -47,9 +47,17 @@ def run(self): last_obs.refresh_from_db() # Gets the record updates # Boilerplate to get necessary properties for future calls - start_keyword, end_keyword = facility.get_start_end_keywords() observation_payload = last_obs.parameters + scheduled_start = last_obs.scheduled_start + scheduled_end = last_obs.scheduled_end + + # Add the scheduled start and end + observation_payload['scheduled_start'] = scheduled_start + observation_payload['scheduled_end'] = scheduled_end + start_keyword = 'scheduled_start' + end_keyword = 'scheduled_end' + # Cadence logic # If the observation hasn't finished, do nothing if not last_obs.terminal: @@ -95,6 +103,7 @@ def run(self): for obsr in new_observations: facility = get_service_class(obsr.facility)() facility.update_observation_status(obsr.observation_id) + obsr.refresh_from_db() # commit the updated observation status return new_observations From 56841536628fe952c9b83136317dd2fd68ef637d Mon Sep 17 00:00:00 2001 From: Moira Andrews Date: Wed, 1 Apr 2026 12:10:51 -0400 Subject: [PATCH 2/7] simplifying variables --- tom_observations/cadences/resume_cadence_after_failure.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/tom_observations/cadences/resume_cadence_after_failure.py b/tom_observations/cadences/resume_cadence_after_failure.py index 963a74bf8..6d01bd88e 100644 --- a/tom_observations/cadences/resume_cadence_after_failure.py +++ b/tom_observations/cadences/resume_cadence_after_failure.py @@ -49,12 +49,9 @@ def run(self): # Boilerplate to get necessary properties for future calls observation_payload = last_obs.parameters - scheduled_start = last_obs.scheduled_start - scheduled_end = last_obs.scheduled_end - # Add the scheduled start and end - observation_payload['scheduled_start'] = scheduled_start - observation_payload['scheduled_end'] = scheduled_end + observation_payload['scheduled_start'] = last_obs.scheduled_start + observation_payload['scheduled_end'] = last_obs.scheduled_end start_keyword = 'scheduled_start' end_keyword = 'scheduled_end' From 2382974ee80040a35ac05fe47487a4ab2be02bef Mon Sep 17 00:00:00 2001 From: Moira Andrews Date: Wed, 1 Apr 2026 13:43:26 -0400 Subject: [PATCH 3/7] Updated logic to be a shorter window --- .../cadences/resume_cadence_after_failure.py | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/tom_observations/cadences/resume_cadence_after_failure.py b/tom_observations/cadences/resume_cadence_after_failure.py index 6d01bd88e..44868f4b6 100644 --- a/tom_observations/cadences/resume_cadence_after_failure.py +++ b/tom_observations/cadences/resume_cadence_after_failure.py @@ -43,17 +43,15 @@ def run(self): # Make a call to the facility to get the current status of the observation facility = get_service_class(last_obs.facility)() + start_keyword, end_keyword = facility.get_start_end_keywords() facility.update_observation_status(last_obs.observation_id) # Updates the DB record last_obs.refresh_from_db() # Gets the record updates # Boilerplate to get necessary properties for future calls observation_payload = last_obs.parameters - # Add the scheduled start and end - observation_payload['scheduled_start'] = last_obs.scheduled_start + # Only needs the scheduled end from the observatory observation_payload['scheduled_end'] = last_obs.scheduled_end - start_keyword = 'scheduled_start' - end_keyword = 'scheduled_end' # Cadence logic # If the observation hasn't finished, do nothing @@ -61,7 +59,11 @@ def run(self): return elif last_obs.failed: # If the observation failed # Submit next observation to be taken as soon as possible with the same window length - window_length = parse(observation_payload[end_keyword]) - parse(observation_payload[start_keyword]) + # Make the window length the cadence frequency or 24 hours, whichever is shorter. + cadence_frequency = self.dynamic_cadence.cadence_parameters.get('cadence_frequency') + if not cadence_frequency: + raise Exception(f'The {self.name} strategy requires a cadence_frequency cadence_parameter.') + window_length = 24 if cadence_frequency > 24 else cadence_frequency observation_payload[start_keyword] = datetime.now().isoformat() observation_payload[end_keyword] = (parse(observation_payload[start_keyword]) + window_length).isoformat() else: # If the observation succeeded @@ -109,9 +111,13 @@ def advance_window(self, observation_payload, start_keyword='start', end_keyword if not cadence_frequency: raise Exception(f'The {self.name} strategy requires a cadence_frequency cadence_parameter.') advance_window_hours = cadence_frequency - window_length = parse(observation_payload[end_keyword]) - parse(observation_payload[start_keyword]) - new_start = parse(observation_payload[start_keyword]) + timedelta(hours=advance_window_hours) + # Window length for the observation should be every 24 hours unless the frequency is less than 24 + # then just the cadence frequency + window_length = 24 if cadence_frequency > 24 else cadence_frequency + + # Define new start to be at the end of the previous observation + cadence in hours + new_start = parse(observation_payload['scheduled_end']) + timedelta(hours=advance_window_hours) if new_start < datetime.now(): # Ensure that the new window isn't in the past new_start = datetime.now() new_end = new_start + window_length From 4df78e9bb473fdb75ed114e74b857f80927bb561 Mon Sep 17 00:00:00 2001 From: Moira Andrews Date: Wed, 1 Apr 2026 17:03:36 -0400 Subject: [PATCH 4/7] changed 24 hour to a settings variable call --- .../cadences/resume_cadence_after_failure.py | 9 +++++++-- .../cadences/retry_failed_observations.py | 11 ++++++++--- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/tom_observations/cadences/resume_cadence_after_failure.py b/tom_observations/cadences/resume_cadence_after_failure.py index 44868f4b6..f03b6eaa5 100644 --- a/tom_observations/cadences/resume_cadence_after_failure.py +++ b/tom_observations/cadences/resume_cadence_after_failure.py @@ -1,5 +1,6 @@ from datetime import datetime, timedelta from dateutil.parser import parse +from django.conf import settings import logging from tom_observations.cadence import BaseCadenceForm, CadenceStrategy @@ -113,8 +114,12 @@ def advance_window(self, observation_payload, start_keyword='start', end_keyword advance_window_hours = cadence_frequency # Window length for the observation should be every 24 hours unless the frequency is less than 24 - # then just the cadence frequency - window_length = 24 if cadence_frequency > 24 else cadence_frequency + # then just the cadence frequency (24 hour window defined in the settings) + if settings.OBS_WINDOW_MINIMUM: + min_window = settings.OBS_WINDOW_MINIMUM + else: + min_window = 24 + window_length = min_window if cadence_frequency > min_window else cadence_frequency # Define new start to be at the end of the previous observation + cadence in hours new_start = parse(observation_payload['scheduled_end']) + timedelta(hours=advance_window_hours) diff --git a/tom_observations/cadences/retry_failed_observations.py b/tom_observations/cadences/retry_failed_observations.py index fc15217bb..fc4845004 100644 --- a/tom_observations/cadences/retry_failed_observations.py +++ b/tom_observations/cadences/retry_failed_observations.py @@ -1,5 +1,6 @@ from datetime import timedelta from dateutil.parser import parse +from django.conf import settings from tom_observations.cadence import BaseCadenceForm, CadenceStrategy from tom_observations.models import ObservationRecord @@ -57,9 +58,13 @@ def advance_window(self, observation_payload, start_keyword='start', end_keyword cadence_frequency = self.dynamic_cadence.cadence_parameters.get('cadence_frequency') if not cadence_frequency: raise Exception(f'The {self.name} strategy requires a cadence_frequency cadence_parameter.') - advance_window_hours = cadence_frequency - new_start = parse(observation_payload[start_keyword]) + timedelta(hours=advance_window_hours) - new_end = parse(observation_payload[end_keyword]) + timedelta(hours=advance_window_hours) + if settings.OBS_WINDOW_MINIMUM: + min_window = settings.OBS_WINDOW_MINIMUM + else: + min_window = 24 + window_length = min_window if cadence_frequency > min_window else cadence_frequency + new_start = parse(observation_payload[start_keyword]) + timedelta(hours=window_length) + new_end = parse(observation_payload[end_keyword]) + timedelta(hours=window_length) observation_payload[start_keyword] = new_start.isoformat() observation_payload[end_keyword] = new_end.isoformat() From 6cab1e5c34e73b0882dac475a48de1fbc441b42e Mon Sep 17 00:00:00 2001 From: Moira Andrews Date: Wed, 1 Apr 2026 18:06:20 -0400 Subject: [PATCH 5/7] adds the window minimum to the form clean function --- tom_observations/facilities/lco.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/tom_observations/facilities/lco.py b/tom_observations/facilities/lco.py index 9e96b4972..784a7e686 100644 --- a/tom_observations/facilities/lco.py +++ b/tom_observations/facilities/lco.py @@ -883,7 +883,13 @@ def clean(self): """ cleaned_data = super().clean() start = cleaned_data.get('start') - cleaned_data['end'] = datetime.strftime(parse(start) + timedelta(hours=cleaned_data['cadence_frequency']), + if settings.OBS_WINDOW_MINIMUM: + min_window = settings.OBS_WINDOW_MINIMUM + else: + min_window = 24 + window_length = min_window if cleaned_data['cadence_frequency'] > min_window else cleaned_data['cadence_frequency'] + + cleaned_data['end'] = datetime.strftime(parse(start) + timedelta(hours=window_length), '%Y-%m-%dT%H:%M:%S') return cleaned_data @@ -1058,8 +1064,15 @@ def clean(self): """ cleaned_data = super().clean() cleaned_data['instrument_type'] = '2M0-FLOYDS-SCICAM' # SNEx only submits spectra to FLOYDS + start = cleaned_data.get('start') - cleaned_data['end'] = datetime.strftime(parse(start) + timedelta(hours=cleaned_data['cadence_frequency']), + if settings.OBS_WINDOW_MINIMUM: + min_window = settings.OBS_WINDOW_MINIMUM + else: + min_window = 24 + window_length = min_window if cleaned_data['cadence_frequency'] > min_window else cleaned_data['cadence_frequency'] + + cleaned_data['end'] = datetime.strftime(parse(start) + timedelta(hours=window_length), '%Y-%m-%dT%H:%M:%S') return cleaned_data From b9e8e366b63e4b034d2dddb2573442c3307f6b7c Mon Sep 17 00:00:00 2001 From: Moira Andrews Date: Thu, 2 Apr 2026 14:13:55 -0400 Subject: [PATCH 6/7] updated min window to be consistent and fall back to the cadence frequency --- .../cadences/resume_cadence_after_failure.py | 14 ++++++++++---- .../cadences/retry_failed_observations.py | 7 ++++--- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/tom_observations/cadences/resume_cadence_after_failure.py b/tom_observations/cadences/resume_cadence_after_failure.py index f03b6eaa5..43509776c 100644 --- a/tom_observations/cadences/resume_cadence_after_failure.py +++ b/tom_observations/cadences/resume_cadence_after_failure.py @@ -64,7 +64,12 @@ def run(self): cadence_frequency = self.dynamic_cadence.cadence_parameters.get('cadence_frequency') if not cadence_frequency: raise Exception(f'The {self.name} strategy requires a cadence_frequency cadence_parameter.') - window_length = 24 if cadence_frequency > 24 else cadence_frequency + if settings.OBS_WINDOW_MINIMUM: + window_length = settings.OBS_WINDOW_MINIMUM + if window_length > cadence_frequency: + window_length = cadence_frequency + else: + window_length = cadence_frequency observation_payload[start_keyword] = datetime.now().isoformat() observation_payload[end_keyword] = (parse(observation_payload[start_keyword]) + window_length).isoformat() else: # If the observation succeeded @@ -116,10 +121,11 @@ def advance_window(self, observation_payload, start_keyword='start', end_keyword # Window length for the observation should be every 24 hours unless the frequency is less than 24 # then just the cadence frequency (24 hour window defined in the settings) if settings.OBS_WINDOW_MINIMUM: - min_window = settings.OBS_WINDOW_MINIMUM + window_length = settings.OBS_WINDOW_MINIMUM + if window_length > cadence_frequency: + window_length = cadence_frequency else: - min_window = 24 - window_length = min_window if cadence_frequency > min_window else cadence_frequency + window_length = cadence_frequency # Define new start to be at the end of the previous observation + cadence in hours new_start = parse(observation_payload['scheduled_end']) + timedelta(hours=advance_window_hours) diff --git a/tom_observations/cadences/retry_failed_observations.py b/tom_observations/cadences/retry_failed_observations.py index fc4845004..76833306d 100644 --- a/tom_observations/cadences/retry_failed_observations.py +++ b/tom_observations/cadences/retry_failed_observations.py @@ -59,10 +59,11 @@ def advance_window(self, observation_payload, start_keyword='start', end_keyword if not cadence_frequency: raise Exception(f'The {self.name} strategy requires a cadence_frequency cadence_parameter.') if settings.OBS_WINDOW_MINIMUM: - min_window = settings.OBS_WINDOW_MINIMUM + window_length = settings.OBS_WINDOW_MINIMUM + if window_length > cadence_frequency: + window_length = cadence_frequency else: - min_window = 24 - window_length = min_window if cadence_frequency > min_window else cadence_frequency + window_length = cadence_frequency new_start = parse(observation_payload[start_keyword]) + timedelta(hours=window_length) new_end = parse(observation_payload[end_keyword]) + timedelta(hours=window_length) observation_payload[start_keyword] = new_start.isoformat() From 2d858ceac66d6072e68b69dc675273744b820658 Mon Sep 17 00:00:00 2001 From: Moira Andrews Date: Thu, 2 Apr 2026 14:16:20 -0400 Subject: [PATCH 7/7] updated window length call in facility --- tom_observations/facilities/lco.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tom_observations/facilities/lco.py b/tom_observations/facilities/lco.py index 784a7e686..4f4a6897d 100644 --- a/tom_observations/facilities/lco.py +++ b/tom_observations/facilities/lco.py @@ -883,11 +883,13 @@ def clean(self): """ cleaned_data = super().clean() start = cleaned_data.get('start') + cadence_frequency = cleaned_data['cadence_frequency'] if settings.OBS_WINDOW_MINIMUM: - min_window = settings.OBS_WINDOW_MINIMUM + window_length = settings.OBS_WINDOW_MINIMUM + if window_length > cadence_frequency: + window_length = cadence_frequency else: - min_window = 24 - window_length = min_window if cleaned_data['cadence_frequency'] > min_window else cleaned_data['cadence_frequency'] + window_length = cadence_frequency cleaned_data['end'] = datetime.strftime(parse(start) + timedelta(hours=window_length), '%Y-%m-%dT%H:%M:%S')