diff --git a/SAS/TMSS/backend/services/scheduling/lib/constraints.py b/SAS/TMSS/backend/services/scheduling/lib/constraints.py
index 6bdc533b42d292a51b054b595b5af86b6a716d88..9632d84f2352315af5d3fb4a5ea4ccd9d021e088 100644
--- a/SAS/TMSS/backend/services/scheduling/lib/constraints.py
+++ b/SAS/TMSS/backend/services/scheduling/lib/constraints.py
@@ -1275,66 +1275,76 @@ def evaluate_sky_min_distance_constraint(scheduling_unit: models.SchedulingUnitB
     """ Evaluate the sky min_distance constraint: is it met? and what are the score, optimal- and earliest_possible_start_time?
     """
     result = ConstraintResult(scheduling_unit, 'sky.min_distance', proposed_start_time)
-    constraints = scheduling_unit.scheduling_constraints_doc
+    constraints_doc = scheduling_unit.scheduling_constraints_doc
 
-    if 'sky' not in constraints or 'min_distance' not in constraints['sky']:
+    if 'sky' not in constraints_doc or 'min_distance' not in constraints_doc['sky']:
         return result
 
+    # disect the constraints in a 'target' and 'calibrator' part which works for all versions
+    # In TMSS-2877, constraints template version 9 was created with two sections for 'target' and 'calibrator'.
+    # before version 9, we only had one section which was for the target only.
+    target_constraints = constraints_doc['sky']['min_distance'].get('target', constraints_doc['sky']['min_distance'])
+    calibrator_constraints = constraints_doc['sky']['min_distance'].get('calibrator', {})
+
     # keep track of the smallest actual distance to compute the score
     smallest_actual_distance = 3.1415 # bodies can be at most half a great circle away, which is Pi radians.
 
-    # min_distance constraints are only computed for target observations
     target_obs_tasks = [t for t in scheduling_unit.observation_tasks if t.is_target_observation]
-    for target_obs_task in target_obs_tasks:
-        sap_pointings = get_target_sap_pointings(target_obs_task)
-        if not sap_pointings:
-            logger.warning("SUB id=%s task id=%s could not determine pointing(s) to evaluate sky constraints", scheduling_unit.id, target_obs_task.id)
-            return result
-
-        # since the constraint only applies to the middle of the obs, only consider the proposed_center_time
-        # take along the relative_start_time of this task compared to the scheduling unit's start_time
-        task_proposed_start_time = proposed_start_time + target_obs_task.relative_start_time
-        task_proposed_center_time = task_proposed_start_time + target_obs_task.specified_duration / 2
-        task_proposed_end_time = task_proposed_start_time + target_obs_task.specified_duration
-        gridded_timestamps = (gridder.grid_time(task_proposed_start_time),
-                              gridder.grid_time(task_proposed_center_time),
-                              gridder.grid_time(task_proposed_end_time))
-
-        # currently we only check at bounds and center, we probably want to add some more samples in between later on
-        # loop over all bodies and their respective min_distance constraints
-        for pointing in sap_pointings:
-            for body, min_distance in constraints['sky']['min_distance'].items():
-                for gridded_timestamp in gridded_timestamps:
-                    distances = coordinates_and_timestamps_to_separation_from_bodies(pointing=pointing,
-                                                                                     timestamps=(gridded_timestamp,),
-                                                                                     bodies=[body])
-                    actual_distances = distances[body]
-                    assert (len(actual_distances) == 1)
-                    actual_distance = actual_distances[gridded_timestamp]
-                    # logger.debug("min_distance: SUB id=%s task_id=%s task_name='%s' pointing='%s' distance=%.3f[deg] to body=%s %s min_distance=%.3f[deg] at '%s'",
-                    #              scheduling_unit.id, target_obs_task.id, target_obs_task.name, pointing, actual_distance.degree, body,
-                    #              '<' if actual_distance.rad < min_distance else '>=',
-                    #              Angle(min_distance, astropy.units.rad).degree,
-                    #              gridded_timestamp)
-
-                    # keep track of the smallest actual distance to compute the score
-                    smallest_actual_distance = min(smallest_actual_distance, actual_distance.rad)
-
-                    if actual_distance.rad < min_distance:
-                        # constraint not met. update result, and do early exit.
-                        result.score = 0
-                        result.earliest_possible_start_time = None
-                        result.optimal_start_time = None
-                        result.message = "%s distance=%.3f[deg] to body=%s %s min_distance=%.3f[deg] at '%s'" % (pointing,
-                                                                                                                 actual_distance.degree,
-                                                                                                                 body,
-                                                                                                                 '<' if actual_distance.rad < min_distance else '>=',
-                                                                                                                 Angle(min_distance, astropy.units.rad).degree,
-                                                                                                                 gridded_timestamp)
-                        logger.debug(result)
-                        return result
-                # no early exit, so constraint is met for this station and timestamp
-                # continue with rest of stations & timestamps
+    calibrator_obs_tasks = [t for t in scheduling_unit.observation_tasks if t.is_calibrator_observation]
+
+    for constraints, obs_tasks in ((target_constraints, target_obs_tasks),
+                                   (calibrator_constraints, calibrator_obs_tasks)):
+        # min_distance constraints are only computed for target observations
+        for obs_task in obs_tasks:
+            sap_pointings = get_target_sap_pointings(obs_task) if obs_task.is_target_observation else get_calibrator_sap_pointings(obs_task)
+            if not sap_pointings:
+                logger.warning("SUB id=%s task id=%s could not determine pointing(s) to evaluate sky constraints", scheduling_unit.id, target_obs_task.id)
+                return result
+
+            # since the constraint only applies to the middle of the obs, only consider the proposed_center_time
+            # take along the relative_start_time of this task compared to the scheduling unit's start_time
+            task_proposed_start_time = proposed_start_time + obs_task.relative_start_time
+            task_proposed_center_time = task_proposed_start_time + obs_task.specified_duration / 2
+            task_proposed_end_time = task_proposed_start_time + obs_task.specified_duration
+            gridded_timestamps = (gridder.grid_time(task_proposed_start_time),
+                                  gridder.grid_time(task_proposed_center_time),
+                                  gridder.grid_time(task_proposed_end_time))
+
+            # currently we only check at bounds and center, we probably want to add some more samples in between later on
+            # loop over all bodies and their respective min_distance constraints
+            for pointing in sap_pointings:
+                for body, min_distance in constraints.items():
+                    for gridded_timestamp in gridded_timestamps:
+                        distances = coordinates_and_timestamps_to_separation_from_bodies(pointing=pointing,
+                                                                                         timestamps=(gridded_timestamp,),
+                                                                                         bodies=[body])
+                        actual_distances = distances[body]
+                        assert (len(actual_distances) == 1)
+                        actual_distance = actual_distances[gridded_timestamp]
+                        # logger.debug("min_distance: SUB id=%s task_id=%s task_name='%s' pointing='%s' distance=%.3f[deg] to body=%s %s min_distance=%.3f[deg] at '%s'",
+                        #              scheduling_unit.id, target_obs_task.id, target_obs_task.name, pointing, actual_distance.degree, body,
+                        #              '<' if actual_distance.rad < min_distance else '>=',
+                        #              Angle(min_distance, astropy.units.rad).degree,
+                        #              gridded_timestamp)
+
+                        # keep track of the smallest actual distance to compute the score
+                        smallest_actual_distance = min(smallest_actual_distance, actual_distance.rad)
+
+                        if actual_distance.rad < min_distance:
+                            # constraint not met. update result, and do early exit.
+                            result.score = 0
+                            result.earliest_possible_start_time = None
+                            result.optimal_start_time = None
+                            result.message = "%s distance=%.3f[deg] to body=%s %s min_distance=%.3f[deg] at '%s'" % (pointing,
+                                                                                                                     actual_distance.degree,
+                                                                                                                     body,
+                                                                                                                     '<' if actual_distance.rad < min_distance else '>=',
+                                                                                                                     Angle(min_distance, astropy.units.rad).degree,
+                                                                                                                     gridded_timestamp)
+                            logger.debug(result)
+                            return result
+                    # no early exit, so constraint is met for this station and timestamp
+                    # continue with rest of stations & timestamps
 
         # no early exit, so constraint is met for all stations and timestamps
         result.earliest_possible_start_time = proposed_start_time