diff --git a/config/exp_template.py b/config/exp_template.py
index 37b4a1534a7d2a4430c95f64b1dafe5ab91eaf63..0c97c5191a86ba237bb2befb2986bad77c826597 100755
--- a/config/exp_template.py
+++ b/config/exp_template.py
@@ -29,6 +29,9 @@ exp.dart_nml = {'&assim_tools_nml':
                             output_sd='.true.',
                             stages_to_write='output',
                         ),
+                '&quality_control_nml': 
+                    dict(outlier_threshold='-1',
+                        ),
                 '&location_nml':
                     dict(horiz_dist_only='.true.',
                         ),
diff --git a/dartwrf/assim_synth_obs.py b/dartwrf/assim_synth_obs.py
index a23f804e773979f543176f5ba17bc17105711163..5b335443d88785eda7dbf610e841cab89bdad4cd 100755
--- a/dartwrf/assim_synth_obs.py
+++ b/dartwrf/assim_synth_obs.py
@@ -285,8 +285,11 @@ def qc_obs(time, oso):
         print('saved', f_out_dart)
 
 
-def evaluate(assim_time, obs_seq_out=False,
-             output_format="%Y-%m-%d_%H:%M_obs_seq.final-eval_posterior_allobs"):
+def evaluate(assim_time, 
+             obs_seq_out=False,
+             prior=True, 
+             posterior=False,
+             output_format="%Y-%m-%d_%H:%M_obs_seq.final-evaluate"):
     """Calculates either prior or posterior obs space values.
 
     Note: Depends on a prepared input_list.txt, which defines the ensemble (prior or posterior).
@@ -301,6 +304,8 @@ def evaluate(assim_time, obs_seq_out=False,
     Returns:
         obsseq.ObsSeq
     """
+    if prior == posterior:
+        raise ValueError('either prior or posterior must be True, the other must be False')
 
     os.makedirs(cluster.dart_rundir, exist_ok=True)  # create directory to run DART in
     os.chdir(cluster.dart_rundir)
@@ -313,6 +318,11 @@ def evaluate(assim_time, obs_seq_out=False,
     print("prepare nature")
     prepare_nature_dart(assim_time)  # link WRF files to DART directory
 
+    if posterior:
+        write_list_of_inputfiles_posterior(time)
+    if prior:
+        write_list_of_inputfiles_prior()
+
     if obs_seq_out:
         copy(obs_seq_out, cluster.dart_rundir+'/obs_seq.out')
     else:
@@ -433,7 +443,7 @@ def link_DART_binaries_and_RTTOV_files():
 
         print('prepared DART & RTTOV links in', cluster.dart_rundir)
     except Exception as e:
-        if any([hasattr(obscfg, 'sat_channel') for obscfg in exp.observations]):
+        if any(['sat_channel' in obscfg for obscfg in exp.observations]):
             raise e
         else:
             pass  # we dont need RTTOV anyway
@@ -489,20 +499,21 @@ def main(time, prior_init_time, prior_valid_time, prior_path_exp):
 
     # is any observation error parametrized?
     if any(error_is_parametrized) or do_QC:
-        print(" (optional) evaluate prior for all observations (incl rejected)")
-        evaluate(time, output_format="%Y-%m-%d_%H:%M_obs_seq.final-eval_prior_allobs")
+        print(" (optional) evaluate prior for all observations (incl rejected) ")
+        evaluate(time, prior=True,
+                 output_format="%Y-%m-%d_%H:%M_obs_seq.final-evaluate_prior")
 
     print(" assign observation-errors for assimilation ")
     set_obserr_assimilate_in_obsseqout(oso, outfile=cluster.dart_rundir + "/obs_seq.out")
 
     if do_QC:
-        print(" 2.3) reject observations? ")
+        print(" reject observations? ")
         qc_obs(time, oso)
 
     if prior_inflation_type == '2':
         prepare_inflation_2(time, prior_init_time)
 
-    print(" 3) run filter ")
+    print(" run filter ")
     dart_nml.write_namelist()
     filter(nproc=nproc)
     archive_filteroutput(time)
@@ -510,12 +521,16 @@ def main(time, prior_init_time, prior_valid_time, prior_path_exp):
     if prior_inflation_type == '2':
         archive_inflation_2(time)
 
+    print(" evaluate posterior in observation-space")
     if do_QC:
-        print(" 4) evaluate posterior observations for all observations (incl rejected)")
-        write_list_of_inputfiles_posterior(time)
-        
-        evaluate(time, obs_seq_out=cluster.archivedir+'/obs_seq_out/'+time.strftime('%Y-%m-%d_%H:%M_obs_seq.out-beforeQC'),
-                       output_format="%Y-%m-%d_%H:%M_obs_seq.final-eval_posterior_allobs")
+        f_oso = '%Y-%m-%d_%H:%M_obs_seq.out-beforeQC'  # includes all observations (including rejected ones in qc_obs())
+    else:
+        f_oso = '%Y-%m-%d_%H:%M_obs_seq.out'
+    # evaluate() separately after ./filter is crucial when assimilating cloud variables
+    # as the obs_seq.final returned by ./filter was produced using un-clamped cloud variables
+    evaluate(time, obs_seq_out=cluster.archivedir+'/obs_seq_out/'+time.strftime(f_oso),
+             prior=False, posterior=True,
+             output_format="%Y-%m-%d_%H:%M_obs_seq.final-evaluate_posterior")
 
 
 if __name__ == "__main__":
diff --git a/dartwrf/dart_nml.py b/dartwrf/dart_nml.py
index 84a2412ea0f70675fe4fac77edc522152fc67c84..85716b640511a9f48c2f04aa9d33de66e9830379 100644
--- a/dartwrf/dart_nml.py
+++ b/dartwrf/dart_nml.py
@@ -1,5 +1,5 @@
+import warnings
 from dartwrf.utils import append_file
-
 from dartwrf.exp_config import exp
 from dartwrf.server_config import cluster
 
@@ -191,36 +191,36 @@ def _get_list_of_localizations():
         loc_horiz_rad = to_radian_horizontal(loc_horiz_km)
         l_loc_horiz_rad.append(loc_horiz_rad)
 
-        # compute vertical localization
-
-        # do we have vertical localization?
-        if not hasattr(obscfg, "loc_vert_km") and not hasattr(obscfg, "loc_vert_scaleheight"):
-            l_loc_vert_km.append(-1)
-            l_loc_vert_scaleheight.append(-1)
-            # if not add dummy value
-
-        # choose either localization by height or by scale height
-        if hasattr(obscfg, "loc_vert_km") and hasattr(obscfg, "loc_vert_scaleheight"):
-            raise ValueError("Observation config contains both loc_vert_km and loc_vert_scaleheight. Please choose one.")
-        
-        elif hasattr(obscfg, "loc_vert_km"):  # localization by height
+        try:  # localization by height
             loc_vert_km = obscfg["loc_vert_km"]
 
             vert_norm_hgt = to_vertical_normalization(loc_vert_km, loc_horiz_km)
             l_loc_vert_km.append(vert_norm_hgt)
 
-        elif hasattr(obscfg, "loc_vert_scaleheight"):  # localization by scale height
-            loc_vert_scaleheight = obscfg["loc_vert_scaleheight"]
+            # set the other (unused) list to a dummy value
+            l_loc_vert_scaleheight = [-1,]
 
-            # no conversion necessary, take the values as defined in obscfg
-            l_loc_vert_scaleheight.append(loc_vert_scaleheight)
+        except AttributeError:  # localization by scale height
+            try:
+                loc_vert_scaleheight = obscfg["loc_vert_scaleheight"]
 
-    # set the other (unused) list to a dummy value
-    if len(l_loc_vert_km) > 0:
-        l_loc_vert_scaleheight = [-1,]
-    else:
-        l_loc_vert_km = [-1,]
-    
+                # no conversion necessary, take the values as defined in obscfg
+                l_loc_vert_scaleheight.append(loc_vert_scaleheight)
+
+                # set the other (unused) list to a dummy value
+                l_loc_vert_km = [-1,]
+
+            except AttributeError:
+
+                # do we have vertical localization?
+                # check parameter horiz_dist_only == true
+                if exp.dart_nml['&location_nml']['horiz_dist_only'][0] == '.true.':
+                    # no vertical localization
+                    l_loc_vert_km.append(-1)
+                    l_loc_vert_scaleheight.append(-1)
+                else:
+                    raise ValueError('Neither `loc_vert_km` nor `loc_vert_scaleheight` defined in obscfg.')
+                
     return l_obstypes, l_loc_horiz_rad, l_loc_vert_km, l_loc_vert_scaleheight
 
 
@@ -280,15 +280,6 @@ def write_namelist(just_prior_values=False):
     list_obstypes, list_loc_horiz_rad, list_loc_vert_km, list_loc_vert_scaleheight = _get_list_of_localizations()
 
     nml = read_namelist(cluster.dart_srcdir + "/input.nml")
-    
-    # dont compute posterior, just evaluate prior
-    if just_prior_values:  
-        nml['&filter_nml']['compute_posterior'] = [['.false.']]
-        nml['&filter_nml']['output_members'] = [['.false.']]
-        nml['&filter_nml']['output_mean'] = [['.false.']]
-        nml['&filter_nml']['output_sd'] = [['.false.']]
-        nml['&obs_kind_nml']['assimilate_these_obs_types'] = [[]]
-        nml['&obs_kind_nml']['evaluate_these_obs_types'] = [list_obstypes]
 
     if len(list_obstypes) > 0:
         # make sure that observations defined in `exp.observations` are assimilated
@@ -305,6 +296,15 @@ def write_namelist(just_prior_values=False):
         nml['&location_nml']['special_vert_normalization_levels'] = [[-1,]]
         nml['&location_nml']['special_vert_normalization_pressures'] = [[-1,]]
 
+    # dont compute posterior, just evaluate prior
+    if just_prior_values:  
+        nml['&filter_nml']['compute_posterior'] = [['.false.']]
+        nml['&filter_nml']['output_members'] = [['.false.']]
+        nml['&filter_nml']['output_mean'] = [['.false.']]
+        nml['&filter_nml']['output_sd'] = [['.false.']]
+        nml['&obs_kind_nml']['assimilate_these_obs_types'] = [[]]
+        nml['&obs_kind_nml']['evaluate_these_obs_types'] = [list_obstypes]
+
     # overwrite namelist parameters as defined in the experiment configuration
     for section, sdata in exp.dart_nml.items():
 
@@ -330,8 +330,8 @@ def write_namelist(just_prior_values=False):
     # fail if horiz_dist_only == false but observations contain a satellite channel
     if nml['&location_nml']['horiz_dist_only'][0] == '.false.':
         for obscfg in exp.observations:
-            if hasattr(obscfg, "sat_channel"):
-                raise ValueError("Selected vertical localization, but observations contain satellite obs -> Not possible.")
+            if 'sat_channel' in obscfg:
+                warnings.warn("Selected vertical localization, but observations contain satellite obs -> Bug in DART.")
 
     # write to file
     write_namelist_from_dict(nml, cluster.dart_rundir + "/input.nml")
diff --git a/dartwrf/evaluate_obs_space.py b/dartwrf/evaluate_obs_space.py
index 0436d926e3e075bbafb91b70a4558052c6e05f86..27269103e251934b50eaf87005c7878f2e049a53 100755
--- a/dartwrf/evaluate_obs_space.py
+++ b/dartwrf/evaluate_obs_space.py
@@ -1,49 +1,59 @@
 import os, sys, shutil, warnings
-import time as time_module
 import datetime as dt
-import numpy as np
 
 from dartwrf.exp_config import exp
 from dartwrf.server_config import cluster
 from dartwrf import assim_synth_obs as aso
 
-def get_previous_obsseq_file(time):
-     oso_input = cluster.archivedir+'/obs_seq_out' + time.strftime("/%Y-%m-%d_%H:%M_obs_seq.out-beforeQC")
+def evaluate_one_time(init, valid):
+     """Evaluate the ensemble forecast in observation space at a given time, apart from the analysis time.
+
+     Args:
+          init (datetime): initialization time of the forecast
+          valid (datetime): valid time of the forecast
+
+     Returns:
+          None
+     """
+
+     # # prepare nature and prior ensemble
+     aso.prepare_nature_dart(valid)
+     aso.prepare_prior_ensemble(valid, prior_init_time=init, prior_valid_time=valid, prior_path_exp=cluster.archivedir)
+
 
-     if not os.path.isfile(oso_input):  # fallback
-          oso_input = cluster.archivedir+'/obs_seq_out' + time.strftime("/%Y-%m-%d_%H:%M_obs_seq.out")
+     if use_other_obsseq:  # use a different obsseq file
+          f_obs_seq_out = use_other_obsseq
+          shutil.copy(f_obs_seq_out, cluster.dart_rundir+'/obs_seq.out')
+
+     else:  # from same exp
+          # use the last assimilation obsseq file for the observation locations (note: observed values are not valid)     
+          f_obs_seq_out = cluster.archivedir+valid.strftime("/obs_seq_out/%Y-%m-%d_%H:%M_obs_seq.out")
 
-     return oso_input
+          from dartwrf.obs import obsseq
+          oso = obsseq.ObsSeq(f_obs_seq_out)
+          oso.df['observations'] = -9999
+          oso.to_dart(cluster.dart_rundir+'/obs_seq.out')
 
+     aso.evaluate(valid, output_format="%Y-%m-%d_%H:%M_obs_seq.final-evaluate")
 
 
 if __name__ == "__main__":
      """Evaluate the ensemble forecast in observation space at a given time, apart from the analysis time.
 
      Note: Observations are not assimilated. This is only for evaluation purposes.
-     """
 
-     init = dt.datetime.strptime(sys.argv[1], "%Y-%m-%d_%H:%M")
-     time = dt.datetime.strptime(sys.argv[2], "%Y-%m-%d_%H:%M")
+     Usage: python3 evaluate_obs_space.py init1,valid1 init2,valid2 ...
+     """
+     args = sys.argv[1:]
+     init_valid_tuples = [a.split(',') for a in args]
 
      use_other_obsseq = False
 
      # we need an existing run_DART folder
      aso.prepare_run_DART_folder()
 
-     # # prepare nature and prior ensemble
-     aso.prepare_nature_dart(time)
-     aso.prepare_prior_ensemble(time, prior_init_time=init, prior_valid_time=time, prior_path_exp=cluster.archivedir)
-
-     # tell DART to use the prior as input
-     aso.write_list_of_inputfiles_prior()
-
-     if use_other_obsseq:  # use a different obsseq file
-          oso_input = use_other_obsseq
-     else:  # from same exp
-
-          # use the last assimilation obsseq file for the observation locations (note: observed values are not valid)     
-          oso_input = get_previous_obsseq_file(init)
-          shutil.copy(oso_input, cluster.dart_rundir+'/obs_seq.out')
+     for (init, valid) in init_valid_tuples:
 
-     aso.evaluate(time, output_format="%Y-%m-%d_%H:%M_obs_seq.final-evaluate")
\ No newline at end of file
+          init = dt.datetime.strptime(init, "%Y-%m-%d_%H:%M")
+          valid = dt.datetime.strptime(valid, "%Y-%m-%d_%H:%M")
+          evaluate_one_time(init, valid)
\ No newline at end of file
diff --git a/dartwrf/obs/create_obsseq_in.py b/dartwrf/obs/create_obsseq_in.py
index ebcbed58a36b22a5351e3603308739dfa9618f53..ca990517a3bd7eb52e5e9b7c28240a509af508c3 100755
--- a/dartwrf/obs/create_obsseq_in.py
+++ b/dartwrf/obs/create_obsseq_in.py
@@ -42,7 +42,7 @@ def round_to_day(dtobj):
     return dtobj.replace(second=0, minute=0, hour=0)
 
 
-def add_timezone_UTC(t):
+def _add_timezone_UTC(t):
     return dt.datetime(t.year, t.month, t.day, t.hour, t.minute, tzinfo=dt.timezone.utc)
 
 
@@ -61,14 +61,7 @@ def get_dart_date(time_dt):
     return dart_date_day, secs_thatday
 
 
-def write_tuple_to_pickle(fpath_out, tuple):
-    import pickle
-    os.makedirs(os.path.dirname(fpath_out), exist_ok=True)
-    with open(fpath_out, 'wb') as f:
-        pickle.dump(tuple, f)
-    print(fpath_out, 'saved.')
-
-def write_file(msg, output_path='./'):
+def _write_file(msg, output_path='./'):
     try:
         os.remove(output_path)
     except OSError:
@@ -79,7 +72,7 @@ def write_file(msg, output_path='./'):
         print(output_path, 'saved.')
 
 
-def append_hgt_to_coords(coords, heights):
+def _append_hgt_to_coords(coords, heights):
     coords2 = []
     for i in range(len(coords)):
         for hgt_m in heights:
@@ -108,7 +101,7 @@ obs_kind_definitions
   first:            1  last:            """+n_obs_str
 
 
-def determine_vert_coords(sat_channel, kind, obscfg):
+def _determine_vert_coords(sat_channel, kind, obscfg):
     if not sat_channel:
         if 'SYNOP' in kind:
             vert_coord_sys = "-1"  # meters AGL
@@ -124,6 +117,15 @@ def determine_vert_coords(sat_channel, kind, obscfg):
 
 def write_sat_angle_appendix(sat_channel, lat0, lon0, time_dt):
     """Writes metadata str for an observation inside obs_seq.out
+
+    Args:
+        sat_channel (int or False): False if not a satellite observation
+        lat0 (float): latitude of point on earth
+        lon0 (float): longitude of point on earth
+        time_dt (dt.datetime): time of observation
+
+    Returns:
+        str
     """
     if sat_channel:
         sun_az = str(get_azimuth(lat0, lon0, time_dt))
@@ -146,6 +148,9 @@ def write_section(obs, last=False):
     Args:
         obs (object)
         last (bool): True if this is the last observation in the obs_seq file
+
+    Returns:
+        str
     """
     lon_rad = str(degr_to_rad(obs['lon']))
     lat_rad = str(degr_to_rad(obs['lat']))
@@ -188,7 +193,7 @@ def create_obs_seq_in(time_dt, list_obscfg,
     os.makedirs(os.path.dirname(output_path), exist_ok=True)
 
     print('creating obs_seq.in:')
-    time_dt = add_timezone_UTC(time_dt)
+    time_dt = _add_timezone_UTC(time_dt)
     dart_date_day, secs_thatday = get_dart_date(time_dt)
 
     txt = ''
@@ -216,8 +221,8 @@ def create_obs_seq_in(time_dt, list_obscfg,
         sat_channel = obscfg.get('sat_channel', False)
 
         # add observation locations in the vertical at different levels
-        vert_coord_sys, vert_coords = determine_vert_coords(sat_channel, kind, obscfg)
-        coords = append_hgt_to_coords(coords, vert_coords)
+        vert_coord_sys, vert_coords = _determine_vert_coords(sat_channel, kind, obscfg)
+        coords = _append_hgt_to_coords(coords, vert_coords)
         n_obs_3d_thistype = len(coords)
 
         # user defined generation error
@@ -252,57 +257,19 @@ def create_obs_seq_in(time_dt, list_obscfg,
     pretxt = preamble(n_obs_total, list_kinds)
 
     alltxt = pretxt + txt
-    write_file(alltxt, output_path=output_path)
+    _write_file(alltxt, output_path=output_path)
 
 
 if __name__ == '__main__':
     # for testing
     time_dt = dt.datetime(2008, 7, 30, 13, 0)
 
-
-    vis = dict(plotname='VIS 0.6µm',  plotunits='[1]',
-            kind='MSG_4_SEVIRI_BDRF', sat_channel=1, 
-            n_obs=961, obs_locations='square_array_evenly_on_grid',
-            error_generate=0.03, error_assimilate=0.06,
-            cov_loc_radius_km=32)
-
-    # wv62 = dict(plotname='Brightness temperature WV 6.2µm', plotunits='[K]',
-    #             kind='MSG_4_SEVIRI_TB', sat_channel=5, 
-    #             n_obs=n_obs, obs_locations='square_array_evenly_on_grid',
-    #             error_generate=1., error_assimilate=False, 
-    #             cov_loc_radius_km=20)
-
-    # wv73 = dict(plotname='Brightness temperature WV 7.3µm',  plotunits='[K]',
-    #             kind='MSG_4_SEVIRI_TB', sat_channel=6, 
-    #             n_obs=n_obs, obs_locations='square_array_evenly_on_grid',
-    #             error_generate=1., error_assimilate=False,
-    #             cov_loc_radius_km=20)
-
-    # ir108 = dict(plotname='Brightness temperature IR 10.8µm', plotunits='[K]',
-    #             kind='MSG_4_SEVIRI_TB', sat_channel=9, 
-    #             n_obs=n_obs, obs_locations='square_array_evenly_on_grid',
-    #             error_generate=5., error_assimilate=10.,
-    #             cov_loc_radius_km=32)
-
-    radar = dict(plotname='Radar reflectivity', plotunits='[dBz]',
-                kind='RADAR_REFLECTIVITY',             
-                n_obs=1, obs_locations=[(45,0),],
-                error_generate=2.5, error_assimilate=5.,
-                heights=np.arange(1000, 15001, 1000),
-                cov_loc_radius_km=20, cov_loc_vert_km=4)
-
-    # t2m = dict(plotname='SYNOP Temperature', plotunits='[K]',
-    #         kind='SYNOP_TEMPERATURE',             
-    #         n_obs=n_obs, obs_locations='square_array_evenly_on_grid',
-    #         error_generate=0.1, error_assimilate=1.,
-    #         cov_loc_radius_km=20, cov_loc_vert_km=3)
-
-    # psfc = dict(plotname='SYNOP Pressure', plotunits='[dBz]',
-    #             kind='SYNOP_SURFACE_PRESSURE', 
-    #             n_obs=n_obs, obs_locations='square_array_evenly_on_grid',
-    #             error_generate=50., error_assimilate=100.,
-    #             cov_loc_radius_km=32, cov_loc_vert_km=5)
-
+    radar = dict(var_name='Radar reflectivity', unit='[dBz]',
+                kind='RADAR_REFLECTIVITY', 
+                n_obs=5776, obs_locations='square_array_evenly_on_grid',
+                error_generate=2.5, error_assimilate=2.5,
+                heights=range(2000, 14001, 2000),
+                loc_horiz_km=20, loc_vert_km=2.5)
 
     create_obs_seq_in(time_dt, [radar],
                       output_path=utils.userhome+'/run_DART/obs_seq.in')
diff --git a/dartwrf/obs/create_obsseq_out.py b/dartwrf/obs/create_obsseq_out.py
index 69e95e66fdbe6b02340e9b3f597e4559b1fdde3c..af9eaa503b05dc6803879784d1db802fc5970ff9 100644
--- a/dartwrf/obs/create_obsseq_out.py
+++ b/dartwrf/obs/create_obsseq_out.py
@@ -63,14 +63,16 @@ def generate_obsseq_out(time):
     print('saved', f_oso)
     return oso
 
-def run_perfect_model_obs(nproc=12, verbose=True):
-    """Run the perfect_model_obs program to generate observations
+def run_perfect_model_obs(nproc=12):
+    """Run the ./perfect_model_obs program
+
+    Args:
+        nproc (int): number of processors to use
     
     Returns:
-        None
+        None, creates obs_seq.out
     """
-    if verbose:
-        print("generating observations - running ./perfect_model_obs")
+    print("running ./perfect_model_obs")
     os.chdir(cluster.dart_rundir)
 
     try_remove(cluster.dart_rundir + "/obs_seq.out")
@@ -90,6 +92,9 @@ if __name__ == '__main__':
     
     Args:
         times (str): comma-separated list of times of the observations
+
+    Returns:
+        None, creates obs_seq.out in cluster.archivedir
     """
     import argparse
     import datetime as dt
diff --git a/dartwrf/obs/error_models.py b/dartwrf/obs/error_models.py
index 08c3236fbf24c8bb85fdd76d379adff6f964d51d..9ec0eae7ee641a267205feba6cb070aa9b369f91 100644
--- a/dartwrf/obs/error_models.py
+++ b/dartwrf/obs/error_models.py
@@ -3,15 +3,17 @@ from scipy.interpolate import interp1d
 
 def calc_obserr_WV(channel, Hx_nature, Hx_prior):
     """Calculate parametrized error (for assimilation)
+
     Args:
-        channel (str):              satellite channel
-        Hx_nature (np.array):       dim (observations)
-        Hx_prior (np.array):        dim (ensemble_members, observations)
+        channel (str):          satellite channel
+        Hx_nature (np.array):   H(x_nature) with dimension (observations)
+        Hx_prior (np.array):    H(x_prior) with dimension (ensemble_members, observations)
 
     Returns
-        np.array        Observation error std-deviation with dim (observations)
+        np.array        Observation error std-deviation with dimension (observations)
     """
-    assert channel in ['WV62', 'WV73']
+    if channel not in ['WV62', 'WV73']:
+        raise NotImplementedError("channel not implemented: " + channel)
     debug = False
 
     n_obs = len(Hx_nature)
@@ -21,13 +23,13 @@ def calc_obserr_WV(channel, Hx_nature, Hx_prior):
         bt_x_ens = Hx_prior[:, iobs]
         
         # compute Cloud impact for every pair (ensemble, truth)
-        CIs = [cloudimpact(channel, bt_x, bt_y) for bt_x in bt_x_ens]
+        CIs = [_cloudimpact(channel, bt_x, bt_y) for bt_x in bt_x_ens]
         mean_CI = np.mean(CIs)
 
         if channel == 'WV62':
-            oe_model = OE_model_harnisch_WV62(mean_CI)
+            oe_model = _OE_model_harnisch_WV62(mean_CI)
         elif channel == 'WV73':
-            oe_model = OE_model_harnisch_WV73(mean_CI)
+            oe_model = _OE_model_harnisch_WV73(mean_CI)
         
         if debug:
             print("BT_nature=", bt_y, "=> mean_CI=", mean_CI, "=> OE_assim=", oe_model)
@@ -36,7 +38,7 @@ def calc_obserr_WV(channel, Hx_nature, Hx_prior):
     return OEs
 
 
-def cloudimpact(channel, bt_mod, bt_obs):
+def _cloudimpact(channel, bt_mod, bt_obs):
     """
     follows Harnisch 2016, Figure 3
     """
@@ -53,7 +55,7 @@ def cloudimpact(channel, bt_mod, bt_obs):
     return ci
 
 
-def OE_model_harnisch_WV62(ci):
+def _OE_model_harnisch_WV62(ci):
     if ci >= 0 and ci < 7.5:
         # Kelvin, fit of Fig 7a, Harnisch 2016
         x_ci = [0, 2.5, 4.5, 5.5, 7.5]  # average cloud impact [K]
@@ -64,7 +66,7 @@ def OE_model_harnisch_WV62(ci):
         return 6.5
 
 
-def OE_model_harnisch_WV73(ci):
+def _OE_model_harnisch_WV73(ci):
     if ci >= 0 and ci < 16:
         # Kelvin, fit of Fig 7b, Harnisch 2016
         x_ci = [0, 5, 10.5, 13, 16]  # average cloud impact [K]
diff --git a/dartwrf/obs/obsseq.py b/dartwrf/obs/obsseq.py
index cbfa62213a43d63d74464593938079eff07c760e..49f3bd80498f2a3888014166b294af31cb187d44 100755
--- a/dartwrf/obs/obsseq.py
+++ b/dartwrf/obs/obsseq.py
@@ -36,7 +36,6 @@ import os, warnings
 import numpy as np
 import pandas as pd
 
-
 def _plot_box(m, lat, lon, label="", **kwargs):
     """"Draw bounding box
 
diff --git a/dartwrf/obs/obsseq_2dim.py b/dartwrf/obs/obsseq_2dim.py
index 7239906206672579edf2320578784032fc0bec3a..40afa4a7c51999d5a518d73fc3405d6d16cf2be0 100755
--- a/dartwrf/obs/obsseq_2dim.py
+++ b/dartwrf/obs/obsseq_2dim.py
@@ -14,18 +14,23 @@ Example:
 """
 
 from copy import copy
-import os, sys, shutil, warnings
-import time as time_module
+import os, sys, warnings
 import datetime as dt
 import numpy as np
 
 from dartwrf.server_config import cluster
 from dartwrf import utils
-from dartwrf import assim_synth_obs as aso
 from dartwrf.obs import obsseq
 
 def _get_n_obs_per_layer(oso):     
-     """Get number of observations per layer"""
+     """Determine number of observations per layer from obsseq.ObsSeq object
+
+     Args:
+          oso (obsseq.ObsSeq): obsseq object
+
+     Returns:
+          int
+     """
      height_all = np.array([a[2] for a in oso.df.loc3d])
 
      height_first = height_all[0]
diff --git a/dartwrf/workflows.py b/dartwrf/workflows.py
index f7914d2a985b0c9bac4e7df7c585c0c95e1df6c5..9122a18278e6bb0dc79788d35066b0a3b58a497f 100644
--- a/dartwrf/workflows.py
+++ b/dartwrf/workflows.py
@@ -117,7 +117,8 @@ class WorkFlows(object):
         _copy_dartwrf_to_archive()  # includes config files
 
         # we set the path from where python should import dartwrf modules
-        self.cluster.python = 'export PYTHONPATH='+self.cluster.scripts_rundir+'; '+self.cluster.python
+        # pythonpath is the folder above the dartwrf folder
+        self.cluster.python = 'export PYTHONPATH='+self.cluster.scripts_rundir+'/../; '+self.cluster.python
 
         # Set paths and backup scripts
         self.cluster.log_dir = self.cluster.archivedir+'/logs/'
@@ -322,7 +323,26 @@ class WorkFlows(object):
         id = self.cluster.run_job("obsseq_netcdf", cfg_update={"time": "10", "mail-type": "FAIL,END"}, 
                 depends_on=[depends_on])
         return id
+    
+    def evaluate_plus1(self, list_assim_times, depends_on=None):
+        list_of_tuples = [(init, (init+dt.timedelta(minutes=1))) for init in list_assim_times]
+        arg = ' '.join([ttuple[0].strftime('%Y-%m-%d_%H:%M,')+ttuple[1].strftime('%Y-%m-%d_%H:%M') for ttuple in list_of_tuples])
+
+        cmd = self.cluster.python+' '+self.cluster.scripts_rundir+'/evaluate_obs_space.py '+arg
+        id = self.cluster.run_job(cmd, 'eval+1'+self.exp.expname, cfg_update={"ntasks": "12", "mem": "50G", "ntasks-per-node": "12", "ntasks-per-core": "2", 
+                                                                              "time": "15", "mail-type": "FAIL"}, 
+                depends_on=[depends_on])
+        return id
+
+    def evaluate_plus0(self, list_assim_times, depends_on=None):
+        list_of_tuples = [(init, init) for init in list_assim_times]
+        arg = ' '.join([ttuple[0].strftime('%Y-%m-%d_%H:%M,')+ttuple[1].strftime('%Y-%m-%d_%H:%M') for ttuple in list_of_tuples])
 
+        cmd = self.cluster.python+' '+self.cluster.scripts_rundir+'/evaluate_obs_space.py '+arg
+        id = self.cluster.run_job(cmd, 'eval+0'+self.exp.expname, cfg_update={"ntasks": "12", "mem": "50G", "ntasks-per-node": "12", "ntasks-per-core": "2", 
+                                                                              "time": "15", "mail-type": "FAIL"}, 
+                depends_on=[depends_on])
+        return id
 
     def verify_sat(self, depends_on=None):
         cmd = self.cluster.python_verif+' /jetfs/home/lkugler/osse_analysis/plot_from_raw/analyze_fc.py '+self.exp.expname+' has_node sat verif1d FSS BS'
diff --git a/docs/source/dartwrf.obs.rst b/docs/source/dartwrf.obs.rst
index 79a1e0e57472de8c7562aacda2720bb20ffb10f3..6106c161cb073f643cf7a5b703934c589d9f841e 100644
--- a/docs/source/dartwrf.obs.rst
+++ b/docs/source/dartwrf.obs.rst
@@ -12,6 +12,22 @@ dartwrf.obs.calculate\_obs\_locations module
    :undoc-members:
    :show-inheritance:
 
+dartwrf.obs.create\_obsseq\_in module
+-------------------------------------
+
+.. automodule:: dartwrf.obs.create_obsseq_in
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+dartwrf.obs.create\_obsseq\_out module
+--------------------------------------
+
+.. automodule:: dartwrf.obs.create_obsseq_out
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
 dartwrf.obs.error\_models module
 --------------------------------
 
@@ -20,6 +36,46 @@ dartwrf.obs.error\_models module
    :undoc-members:
    :show-inheritance:
 
+dartwrf.obs.obskind module
+--------------------------
+
+.. automodule:: dartwrf.obs.obskind
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+dartwrf.obs.obsseq module
+-------------------------
+
+.. automodule:: dartwrf.obs.obsseq
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+dartwrf.obs.obsseq\_2dim module
+-------------------------------
+
+.. automodule:: dartwrf.obs.obsseq_2dim
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+dartwrf.obs.obsseq\_to\_netcdf module
+-------------------------------------
+
+.. automodule:: dartwrf.obs.obsseq_to_netcdf
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+dartwrf.obs.run\_obs\_diag module
+---------------------------------
+
+.. automodule:: dartwrf.obs.run_obs_diag
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
 Module contents
 ---------------
 
diff --git a/docs/source/dartwrf.rst b/docs/source/dartwrf.rst
index d8bab9e076cbad9ecc730a1b560fc0105e690691..9d0143c46d34ab052303f4ca8160869ca0e72ec1 100644
--- a/docs/source/dartwrf.rst
+++ b/docs/source/dartwrf.rst
@@ -28,14 +28,6 @@ dartwrf.create\_obs\_upfront module
    :undoc-members:
    :show-inheritance:
 
-dartwrf.create\_obsseq module
------------------------------
-
-.. automodule:: dartwrf.create_obsseq
-   :members:
-   :undoc-members:
-   :show-inheritance:
-
 dartwrf.create\_wbubble\_wrfinput module
 ----------------------------------------
 
@@ -84,38 +76,6 @@ dartwrf.exp\_config module
    :undoc-members:
    :show-inheritance:
 
-dartwrf.obskind module
-----------------------
-
-.. automodule:: dartwrf.obskind
-   :members:
-   :undoc-members:
-   :show-inheritance:
-
-dartwrf.obsseq module
----------------------
-
-.. automodule:: dartwrf.obsseq
-   :members:
-   :undoc-members:
-   :show-inheritance:
-
-dartwrf.obsseq\_2dim module
----------------------------
-
-.. automodule:: dartwrf.obsseq_2dim
-   :members:
-   :undoc-members:
-   :show-inheritance:
-
-dartwrf.obsseq\_to\_netcdf module
----------------------------------
-
-.. automodule:: dartwrf.obsseq_to_netcdf
-   :members:
-   :undoc-members:
-   :show-inheritance:
-
 dartwrf.prep\_IC\_prior module
 ------------------------------
 
@@ -140,14 +100,6 @@ dartwrf.prepare\_wrfrundir module
    :undoc-members:
    :show-inheritance:
 
-dartwrf.run\_obs\_diag module
------------------------------
-
-.. automodule:: dartwrf.run_obs_diag
-   :members:
-   :undoc-members:
-   :show-inheritance:
-
 dartwrf.server\_config module
 -----------------------------
 
diff --git a/evaluate_plus1.py b/evaluate_plus1.py
new file mode 100755
index 0000000000000000000000000000000000000000..b128c3b1dc44866356b1f7d0d4a814784f306034
--- /dev/null
+++ b/evaluate_plus1.py
@@ -0,0 +1,23 @@
+#!/usr/bin/python3
+"""
+Evaluate the forecast in observation space one minute after the analysis time.
+"""
+import datetime as dt
+from dartwrf.workflows import WorkFlows
+
+w = WorkFlows(exp_config='exp_nonlin.py', server_config='jet.py')
+
+
+assim_times = [dt.datetime(2008,7,30,12), 
+                dt.datetime(2008,7,30,12,30),  
+                dt.datetime(2008,7,30,13),
+                dt.datetime(2008,7,30,13,30),  
+                dt.datetime(2008,7,30,14),]
+
+# generate observations at +1 minute after the assimilation time
+obs_times = [each+dt.timedelta(minutes=1) for each in assim_times]
+w.generate_obsseq_out(obs_times)
+
+
+# evaluate the forecast at +1 minute after the assimilation time
+w.evaluate_plus1(assim_times)
\ No newline at end of file
diff --git a/tests/test_inputnml.py b/tests/test_inputnml.py
index 6117ef08c0bafd7cd837036c3e1c513be4c3f868..78bf73170855de7af78fab3093dcf3e95e890104 100644
--- a/tests/test_inputnml.py
+++ b/tests/test_inputnml.py
@@ -53,5 +53,13 @@ def test_input_nml():
     
     os.remove(test_output)
 
+def test_get_list_of_localizations():
+
+    output = dart_nml._get_list_of_localizations()
+    assert (['SYNOP_TEMPERATURE'], [0.0015698587127158557], [1274000.0], [-1]) == output
+
+
 if __name__ == '__main__':
     test_input_nml()
+
+    test_get_list_of_localizations()
\ No newline at end of file