diff --git a/scripts/Diffraction/isis_powder/abstract_inst.py b/scripts/Diffraction/isis_powder/abstract_inst.py
index d93e103be057c0158952b1a5d879eea9584a786e..9c87f9002a2b5112dc508878bc8dd8e779a27973 100644
--- a/scripts/Diffraction/isis_powder/abstract_inst.py
+++ b/scripts/Diffraction/isis_powder/abstract_inst.py
@@ -200,7 +200,7 @@ class AbstractInst(object):
         """
         raise NotImplementedError("Create calibration from a silicon run is not yet implemented for this instrument")
 
-    def _normalise_ws(self, ws_to_correct, monitor_ws=None, spline_terms=20):
+    def _normalise_ws(self, ws_to_correct, run_details=None):
         return _empty_hook_return_none()
 
     def _get_monitor_spectra(self, run_number):
@@ -244,9 +244,6 @@ class AbstractInst(object):
     def _apply_solid_angle_efficiency_corr(self, ws_to_correct, vanadium_number=None, run_details=None):
         return ws_to_correct
 
-    def _load_monitor(self, number, cycle):
-        return None
-
     def _apply_van_calibration_tof_rebinning(self, vanadium_ws, tof_rebin_pass, return_units):
         return vanadium_ws
 
@@ -262,7 +259,7 @@ class AbstractInst(object):
     def calculate_focus_binning_params(self, sample):
         return None
 
-    def PEARL_populate_user_dirs(self, run_number):
+    def PEARL_setup_input_directories(self, run_number):
         return None
 
 # ----- Private Implementation ----- #
diff --git a/scripts/Diffraction/isis_powder/calibrate.py b/scripts/Diffraction/isis_powder/calibrate.py
index 116d573c6dff6f99cab985d929d2eb4aefa82bb1..8fa4a9d12765991ce60feb8f96955f69d5f3f5aa 100644
--- a/scripts/Diffraction/isis_powder/calibrate.py
+++ b/scripts/Diffraction/isis_powder/calibrate.py
@@ -7,15 +7,15 @@ import isis_powder.common as common
 
 
 def create_van(instrument, van, empty, output_van_file_name, num_of_splines, absorb, gen_absorb):
-    input_van_ws = common._load_current_normalised_ws(number=van, instrument=instrument)
-    input_empty_ws = common._load_current_normalised_ws(number=empty, instrument=instrument)
+    input_van_ws = common._load_current_normalised_ws(run_number=van, instrument=instrument)
+    input_empty_ws = common._load_current_normalised_ws(run_number=empty, instrument=instrument)
 
     corrected_van_ws = mantid.Minus(LHSWorkspace=input_van_ws, RHSWorkspace=input_empty_ws)
 
     common.remove_intermediate_workspace(input_empty_ws)
     common.remove_intermediate_workspace(input_van_ws)
 
-    run_details = instrument._get_run_details(run_number=van)
+    run_details = instrument._get_run_details(run_number_input=van)
 
     corrected_van_ws = instrument. _apply_van_calibration_tof_rebinning(vanadium_ws=corrected_van_ws,
                                                                         tof_rebin_pass=1, return_units="TOF")
@@ -39,7 +39,7 @@ def create_van(instrument, van, empty, output_van_file_name, num_of_splines, abs
 
     common.remove_intermediate_workspace(corrected_van_ws)
 
-    cycle_information = instrument._get_run_details(run_number=van)
+    cycle_information = instrument._get_run_details(run_number_input=van)
     splined_ws_list = instrument._spline_background(focused_van_file, num_of_splines,
                                                     cycle_information.instrument_version)
     # Figure out who will provide the path name
diff --git a/scripts/Diffraction/isis_powder/common.py b/scripts/Diffraction/isis_powder/common.py
index 142d625405768ba4806809dc8a41980c3a6b1fcb..d088328b264e31ff7825be5eb68ad404af4e04ef 100644
--- a/scripts/Diffraction/isis_powder/common.py
+++ b/scripts/Diffraction/isis_powder/common.py
@@ -6,6 +6,7 @@ import mantid.simpleapi as mantid
 
 # --- Public API --- #
 
+
 def create_calibration_by_names(calibration_runs, startup_objects, grouping_file_name, group_names):
     _create_blank_cal_file(calibration_runs=calibration_runs, group_names=group_names,
                            out_grouping_file_name=grouping_file_name, instrument=startup_objects)
@@ -42,88 +43,71 @@ def _create_blank_cal_file(calibration_runs, out_grouping_file_name, instrument,
     remove_intermediate_workspace(input_ws)
 
 
-def _load_monitor(number, input_dir, instrument):
-    if isinstance(number, int):
-        full_file_path = instrument._generate_input_full_path(run_number=number, input_dir=input_dir)
-        mspectra = instrument._get_monitor_spectra(number)
-        load_monitor_ws = mantid.LoadRaw(Filename=full_file_path, SpectrumMin=mspectra, SpectrumMax=mspectra,
-                                         LoadLogFiles="0")
+def load_monitor(run_numbers, instrument):
+    number_list = generate_run_numbers(run_numbers)
+    monitor_spectra = instrument._get_monitor_spectra(number_list[0])
+
+    if len(number_list) == 1:
+        file_name = instrument._generate_inst_file_name(run_number=number_list[0])
+        load_monitor_ws = mantid.Load(Filename=file_name, LoadLogFiles="0",
+                                      SpectrumMin=monitor_spectra, SpectrumMax=monitor_spectra)
     else:
-        load_monitor_ws = _load_monitor_sum_range(files=number, input_dir=input_dir, instrument=instrument)
+        load_monitor_ws = _load_sum_file_range(run_numbers=number_list, instrument=instrument)
+        load_monitor_ws = mantid.ExtractSingleSpectrum(InputWorkspace=load_monitor_ws, WorkspaceIndex=monitor_spectra)
 
     return load_monitor_ws
 
 
-def _load_monitor_sum_range(files, input_dir, instrument):
-    # TODO refactor this - it probably doesn't work at the moment
-    loop = 0
-    num = files.split("_")
-    frange = xrange(int(num[0]), int(num[1]) + 1)
-    mspectra = instrument._get_monitor_spectra(int(num[0]))
-    for i in frange:
-        file_path = instrument._generate_input_full_path(i, input_dir)
-        outwork = "mon" + str(i)
-        mantid.LoadRaw(Filename=file_path, OutputWorkspace=outwork, SpectrumMin=mspectra, SpectrumMax=mspectra,
-                       LoadLogFiles="0")
-        loop += 1
-        if loop == 2:
-            firstwk = "mon" + str(i - 1)
-            secondwk = "mon" + str(i)
-            load_monitor_summed = mantid.Plus(LHSWorkspace=firstwk, RHSWorkspace=secondwk)
-            mantid.mtd.remove(firstwk)
-            mantid.mtd.remove(secondwk)
-        elif loop > 2:
-            secondwk = "mon" + str(i)
-            load_monitor_summed = mantid.Plus(LHSWorkspace=load_monitor_summed, RHSWorkspace=secondwk)
-            mantid.mtd.remove(secondwk)
-
-    return load_monitor_summed
+def _load_raw_files(run_number, instrument):
+    run_number_list = generate_run_numbers(run_number_string=run_number)
+    instrument.PEARL_setup_input_directories(run_number=run_number_list[0])
+    if len(run_number_list) == 1:
+        run_number = instrument._generate_inst_file_name(run_number=run_number_list[0])
+        load_raw_ws = mantid.Load(Filename=run_number, LoadLogFiles="0")
+    else:
+        load_raw_ws = _load_sum_file_range(run_number_list, instrument)
+    return load_raw_ws
 
 
-def _load_raw_files(run_number, instrument):
-    run_name = instrument._generate_inst_file_name(run_number=run_number)
+def _load_sum_file_range(run_numbers, instrument):
+    read_ws_list = []
+    for run_number in run_numbers:
+        file_name = instrument._generate_inst_file_name(run_number=run_number)
+        read_ws = mantid.Load(Filename=file_name, LoadLogFiles="0")
+        output_name = "read_ws_output-" + str(g_ads_workaround["read_ws"])
+        g_ads_workaround["read_ws"] += 1
+        read_ws_list.append(mantid.RenameWorkspace(InputWorkspace=read_ws, OutputWorkspace=output_name))
 
-    instrument.PEARL_populate_user_dirs(run_number=run_number)
+    # Sum all workspaces
+    out_ws_name = "summed_range_load-" + str(g_ads_workaround["read_ws"])
+    g_ads_workaround["read_ws"] += 1
 
-    if isinstance(run_number, int):
-        load_raw_ws = mantid.Load(Filename=run_name, LoadLogFiles="0")
-    else:
-        load_raw_ws = _load_raw_file_range(run_number, instrument)
-    return load_raw_ws
+    summed_ws = mantid.RenameWorkspace(InputWorkspace=read_ws_list[0], OutputWorkspace=out_ws_name)
+    for ws in read_ws_list[1:]:  # Skip first member
+        summed_ws = mantid.Plus(LHSWorkspace=summed_ws, RHSWorkspace=ws, OutputWorkspace=out_ws_name)
+        remove_intermediate_workspace(ws)
+
+    return summed_ws
+
+
+def generate_run_numbers(run_number_string):
+    # Check its not a single run
+    if isinstance(run_number_string, int) or run_number_string.isdigit():
+        return [int(run_number_string)]  # Cast into a list and return
+
+    # If its a string we must parse it
+    run_boundaries = run_number_string.replace('_', '-')  # Accept either _ or - delimiters
+    run_boundaries = run_boundaries.split("-")
+    run_range = xrange(int(run_boundaries[0]), int(run_boundaries[-1]) + 1)  # TODO test for skip conditions
+    return run_range
+
+
+def _load_current_normalised_ws(run_number, instrument):
+    read_in_ws = _load_raw_files(run_number=run_number, instrument=instrument)
 
+    run_information = instrument._get_run_details(run_number_input=run_number)
 
-def _load_raw_file_range(files, instrument):
-    loop = 0
-    num = files.split("_")
-    frange = xrange(int(num[0]), int(num[1]) + 1)
-    out_ws = None
-    for i in frange:
-        file_name = instrument._generate_inst_file_name(run_number=i)
-        outwork = "run" + str(i)
-        mantid.Load(Filename=file_name, OutputWorkspace=outwork, LoadLogFiles="0")
-        loop += 1
-        if loop == 2:
-            firstwk = "run" + str(i - 1)
-            secondwk = "run" + str(i)
-            out_ws = mantid.Plus(LHSWorkspace=firstwk, RHSWorkspace=secondwk)
-            mantid.mtd.remove(firstwk)
-            mantid.mtd.remove(secondwk)
-        elif loop > 2:
-            secondwk = "run" + str(i)
-            out_ws = mantid.Plus(LHSWorkspace=out_ws, RHSWorkspace=secondwk)
-            mantid.mtd.remove(secondwk)
-    return out_ws
-
-
-def _load_current_normalised_ws(number, instrument):
-    # TODO monitor loading should be instrument specific and not in common
-
-    read_in_ws = _load_raw_files(run_number=number, instrument=instrument)
-
-    run_information = instrument._get_run_details(run_number=number)
-    load_monitor_ws = instrument._load_monitor(number=number, cycle=run_information.label)
-
-    read_ws = instrument._normalise_ws(ws_to_correct=read_in_ws, monitor_ws=load_monitor_ws, spline_terms=20)
+    read_ws = instrument._normalise_ws(ws_to_correct=read_in_ws, run_details=run_information)
 
     output_name = "read_ws_output-" + str(g_ads_workaround["read_ws"])
     g_ads_workaround["read_ws"] += 1
diff --git a/scripts/Diffraction/isis_powder/focus.py b/scripts/Diffraction/isis_powder/focus.py
index ff688ffe0fd88f63a49dad69def397b3565d5a6b..bd96d609ecd1235e246a4324ea8e7fbc5040a64c 100644
--- a/scripts/Diffraction/isis_powder/focus.py
+++ b/scripts/Diffraction/isis_powder/focus.py
@@ -11,10 +11,10 @@ def focus(number, instrument, attenuate=True, van_norm=True):
 
 def _run_focus(instrument, run_number, perform_attenuation, perform_vanadium_norm):
     # Read
-    read_ws = common._load_current_normalised_ws(number=run_number, instrument=instrument)
+    read_ws = common._load_current_normalised_ws(run_number=run_number, instrument=instrument)
     input_workspace = instrument._do_tof_rebinning_focus(read_ws)  # Rebins for PEARL
 
-    calibration_file_paths = instrument._get_run_details(run_number=run_number)
+    calibration_file_paths = instrument._get_run_details(run_number_input=run_number)
 
     # Compensate for empty sample if specified
     input_workspace = instrument._subtract_sample_empty(input_workspace)
@@ -55,7 +55,7 @@ def _run_focus(instrument, run_number, perform_attenuation, perform_vanadium_nor
 def _divide_sample_by_vanadium(instrument, run_number, input_workspace, perform_vanadium_norm):
     processed_spectra = []
 
-    run_details = instrument._get_run_details(run_number=run_number)
+    run_details = instrument._get_run_details(run_number_input=run_number)
 
     alg_range, save_range = instrument._get_instrument_alg_save_ranges(run_details.instrument_version)
 
diff --git a/scripts/Diffraction/isis_powder/pearl.py b/scripts/Diffraction/isis_powder/pearl.py
index 0c7c9a6a4297385f07bb434c5a821846aebebb12..1b48f7d69c58a61b9c3886c0f08fa87113deabde 100644
--- a/scripts/Diffraction/isis_powder/pearl.py
+++ b/scripts/Diffraction/isis_powder/pearl.py
@@ -125,7 +125,7 @@ class Pearl(AbstractInst):
         return self._run_attenuate_workspace(input_workspace=input_workspace)
 
     def _create_calibration(self, calibration_runs, offset_file_name, grouping_file_name):
-        input_ws = common._load_current_normalised_ws(number=calibration_runs, instrument=self)
+        input_ws = common._load_current_normalised_ws(run_number=calibration_runs, instrument=self)
         cycle_information = self._get_label_information(calibration_runs)
 
         if cycle_information["instrument_version"] == "new" or cycle_information["instrument_version"] == "new2":
@@ -166,9 +166,12 @@ class Pearl(AbstractInst):
     def _create_calibration_silicon(self, calibration_runs, cal_file_name, grouping_file_name):
         self._do_silicon_calibration(calibration_runs, cal_file_name, grouping_file_name)
 
-    def _normalise_ws(self, ws_to_correct, monitor_ws=None, spline_terms=20):
+    def _normalise_ws(self, ws_to_correct, run_details=None):
+        if not run_details:
+            raise RuntimeError("Run details was not passed into PEARL: normalise_ws")
+        monitor_ws = common.load_monitor(run_numbers=run_details.run_number, instrument=self)
         return self._normalise_current_ws(ws_to_correct=ws_to_correct, load_monitor_ws=monitor_ws,
-                                          spline_terms=spline_terms)
+                                          spline_terms=20)
 
     def _get_monitor_spectra(self, run_number):
         return self._get_monitor_spectrum(run_number=run_number)
@@ -201,19 +204,6 @@ class Pearl(AbstractInst):
                                                            run_number=run_number, perform_attenuation=attenuate,
                                                            focus_mode=self._focus_mode)
 
-    def _load_monitor(self, number, cycle):
-        input_dir = self._generate_raw_data_cycle_dir(run_cycle=cycle)
-        # TODO refactor all of this
-        if isinstance(number, int):
-            full_file_path = self._generate_input_full_path(run_number=number, input_dir=input_dir)
-            mspectra = self._get_monitor_spectra(number)
-            load_monitor_ws = mantid.LoadRaw(Filename=full_file_path, SpectrumMin=mspectra, SpectrumMax=mspectra,
-                                             LoadLogFiles="0")
-        else:
-            load_monitor_ws = common._load_monitor_sum_range(files=number, input_dir=input_dir, instrument=self)
-
-        return load_monitor_ws
-
     def _apply_van_calibration_tof_rebinning(self, vanadium_ws, tof_rebin_pass, return_units):
         tof_rebin_param_dict = self._get_create_van_tof_binning()
         tof_rebin_param = tof_rebin_param_dict[str(tof_rebin_pass)]
@@ -289,7 +279,7 @@ class Pearl(AbstractInst):
 
     def _do_silicon_calibration(self, runs_to_process, cal_file_name, grouping_file_name):
         # TODO fix all of this as the script is too limited to be useful
-        create_si_ws = common._load_current_normalised_ws(number=runs_to_process, instrument=self)
+        create_si_ws = common._load_current_normalised_ws(run_number=runs_to_process, instrument=self)
         cycle_details = self._get_label_information(runs_to_process)
         instrument_version = cycle_details["instrument_version"]
 
@@ -424,7 +414,7 @@ class Pearl(AbstractInst):
     def _PEARL_filename_is_full_path(self):
         return self._old_api_uses_full_paths
 
-    def PEARL_populate_user_dirs(self, run_number):
+    def PEARL_setup_input_directories(self, run_number):
         run_details = self._get_run_details(run_number=run_number)
         generated_path = self._generate_raw_data_cycle_dir(run_cycle=run_details.label)
         user_dirs = config['datasearch.directories']
diff --git a/scripts/Diffraction/isis_powder/polaris.py b/scripts/Diffraction/isis_powder/polaris.py
index 651dfe6513c334153ffa2ad6932f98585b25ae66..2b9a4d104450df16fcd3fc79aa9afca4c931f1e1 100644
--- a/scripts/Diffraction/isis_powder/polaris.py
+++ b/scripts/Diffraction/isis_powder/polaris.py
@@ -47,32 +47,34 @@ class Polaris(AbstractInst):
     def _get_default_group_names(self):
         return self._calibration_grouping_names
 
-    def _get_run_details(self, run_number):
-        # TODO rename this from get calibration to get run details
-        if self._run_details_last_run_number == run_number:
+    def _get_run_details(self, run_number_input):
+        if self._run_details_last_run_number == run_number_input:
             return self._run_details_cached_obj
 
-        configuration = polaris_calib_parser.get_calibration_dict(run_number=run_number)
+        run_number_list = common.generate_run_numbers(run_number_string=run_number_input)
+        configuration = polaris_calib_parser.get_calibration_dict(run_number=run_number_list[0])
         calibration_dir = self.calibration_dir
 
-        # Common to all runs
         calibration_full_path = os.path.join(calibration_dir, configuration["offset_file_name"])
         grouping_full_path = os.path.join(calibration_dir, configuration["grouping_file_name"])
+
         if self._chopper_on:
             chopper_config = configuration["chopper_on"]
         else:
             chopper_config = configuration["chopper_off"]
+
         vanadium_file = self._generate_inst_file_name(run_number=chopper_config["vanadium_file_name"])
         splined_vanadium = os.path.join(calibration_dir, chopper_config["splined_vanadium_file_name"])
         solid_angle_file_path = os.path.join(calibration_dir, chopper_config["solid_angle_file_name"])
 
         calibration_details = RunDetails(calibration_path=calibration_full_path, grouping_path=grouping_full_path,
-                                         vanadium_name=vanadium_file, run_number=run_number)
+                                         vanadium_name=vanadium_file, run_number=run_number_input)
+        calibration_details.label = configuration["label"]
         calibration_details.splined_vanadium = splined_vanadium
         calibration_details.solid_angle_corr = solid_angle_file_path
-        calibration_details.label = configuration["label"]
 
-        self._run_details_last_run_number = run_number
+        # Hold obj in case same run range is requested
+        self._run_details_last_run_number = run_number_input
         self._run_details_cached_obj = calibration_details
 
         return calibration_details
@@ -86,7 +88,7 @@ class Polaris(AbstractInst):
         alg_range = 5
         return alg_range, None
 
-    def _normalise_ws(self, ws_to_correct, monitor_ws=None, spline_terms=20):
+    def _normalise_ws(self, ws_to_correct, run_details=None):
         normalised_ws = mantid.NormaliseByCurrent(InputWorkspace=ws_to_correct)
         return normalised_ws
 
@@ -144,7 +146,7 @@ class Polaris(AbstractInst):
 
     def generate_solid_angle_corrections(self, run_details, vanadium_number):
         if vanadium_number:
-            solid_angle_vanadium_ws = common._load_raw_files(run_number=vanadium_number, instrument=self)
+            solid_angle_vanadium_ws = common._load_raw_files(run_number_list=vanadium_number, instrument=self)
         elif run_details:
             solid_angle_vanadium_ws = mantid.Load(Filename=run_details.vanadium)
         else:
diff --git a/scripts/Diffraction/isis_powder/polaris_routines/polaris_calib_parser.py b/scripts/Diffraction/isis_powder/polaris_routines/polaris_calib_parser.py
index 42d54b3294ee4e028db94950280b7bd34b413011..f9ecfac3cea1f87359b45fca5fd65366c0c845a9 100644
--- a/scripts/Diffraction/isis_powder/polaris_routines/polaris_calib_parser.py
+++ b/scripts/Diffraction/isis_powder/polaris_routines/polaris_calib_parser.py
@@ -7,7 +7,20 @@ import yaml
 
 def get_calibration_dict(run_number):
     config_file = _open_yaml_file()
-    run_key = _get_dictionary_key(config_handle=config_file, run_number=run_number)
+
+    # First check exceptions list as it should be shorter
+    exception_key = _check_if_run_is_exception(config_handle=config_file, run_number=run_number)
+    if exception_key:
+        exceptions_dict = config_file["exceptions"]
+        return exceptions_dict[exception_key]
+
+    # Otherwise parse the entire YAML file
+    run_key = _find_dictionary_key(dict_to_search=config_file, run_number=run_number)
+
+    # If we have not found the run in either
+    if not run_key:
+        raise ValueError("Run number " + str(run_number) + " not recognised in calibration mapping")
+
     return config_file[run_key]
 
 
@@ -27,16 +40,24 @@ def _open_yaml_file():
     return read_config
 
 
-def _get_dictionary_key(config_handle, run_number):
+def _check_if_run_is_exception(config_handle, run_number):
+    try:
+        exceptions_dict = config_handle["exceptions"]
+    except KeyError:
+        return None
+
+    return _find_dictionary_key(dict_to_search=exceptions_dict, run_number=run_number)
+
+
+def _find_dictionary_key(dict_to_search, run_number):
 
-    for key in config_handle:
+    for key in dict_to_search:
         run_generator = _parse_number_key(input_string=key)
         for run_list in run_generator:
             if run_number in run_list:
                 return key
 
-    # If we hit this point the run_number isn't in any of the keys in the YAML file
-    raise ValueError("Run number " + str(run_number) + " not recognised in calibration mapping")
+    return None
 
 
 def _parse_number_key(input_string):